[Java] 익명 클래스와 람다(Lambda) 정리
익명 클래스(Anonymous Class)
익명 클래스란?
익명 클래스는 상속 받는 클래스를 명시적으로 별도의 Java 파일을 통해 클래스를 만드는 것이 아닌, 코드 내부에 이름이 존재하지 않는 클래스를 만드는것이다.
예시 코드
public class Coffee {
public void make() {
System.out.println("Make!!");
}
}
public class Main {
public static void main(String[] args) {
// TODO : Coffee 클래스를 상속 받는 익명 클래스
Coffee coffee = new Coffee() {
// make 메서드 오버라이드
public void make() {
System.out.println("Override Make!!");
}
// 새로운 메서드
public void serve() {
System.out.println("Serve");
}
}
coffee.make();
// Override Make!!
// serve() 메서드는 호출할 수 없다
coffee.serve();
// compile error
}
}
Coffee 클래스를 상속받는 익명 클래스 정의. 별도의 파일을 이용하는 것이 아니라 코드 내부에서 상속받는 클래스를 재정의 하는 방법이다.
coffe.make() 메서드와 다르게 coffee.serve() 메서드는 외부에서 호출할 수 없다. 그 이유는 new Coffee() 를 통해서 생성하는 인스턴스는 Coffee 클래스가 아닌 Coffee 클래스를 상속받는 익명 클래스이기 때문이다. Coffee 클래스 자체에는 serve() 메서드가 선언되지 않았기 때문에 호출할 수 없다.
익명 클래스를 사용하는 이유/이점
"코드를 줄이는 일종의 기법"
익명 클래스는 클래스의 선언과 객체의 생성을 동시에 하기 때문에 단 한번만 사용될 수 있고, 익명으로 정의된 클래스는 일회용으로써 사용되고 버려진다.
만일 부모 클래스의 자원을 상속받아 재정의하여 사용할 자식 클래스가 한번만 사용되고 버려질 자료형이면, 굳이 상단에 클래스를 정의하기보다는, 지역 변수처럼 익명 클래스로 정의와 구현을 동시에 하여 코드를 간결하게 만들 수 있다.
"JVM 메모리 관점에서의 이점"
익명 클래스는 사용이 끝나면 더 이상 참조되지 않게 된다. 이에 따라 스택이 끝나면 가비지 컬렉션의 대상이 되어 메모리를 빠르게 회수 할 수 있다.
1. UI 이벤트 처리, 스레드 객체 등 단발성 이벤트 처리를 위해 프로그램 내에서 한번만 사용되어야 하는 객체일 경우 익명 클래스를 사용할 수 있다.
2. 비즈니스 로직이 제각각이며 재사용성이 전혀 없어 클래스를 생성해야 하는 비용이 더 많을 때도 익명 클래스를 사용할 수 있다.
인터페이스를 구현하는 익명 클래스
public interface Operate {
int operate(int a, int b);
}
public class Calculator {
private int a;
private int b;
public Calculator(int a, int b) {
this.a = a;
this.b = b;
}
public int result(Operate op) {
return op.operate(this.a, this.b);
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator(20, 10);
int result = calculator.result(new Operate() {
@Override
public int operate(int a, int b) {
return a - b;
}
});
System.out.println(result); // 10
}
}
원래는 Operator 인터페이스를 구현한 클래스를 객체를 만들어 Calculator 클래스의 result() 메서드에 파라미터로 넘겨야 한다.
위의 코드는 익명 클래스 방식으로 인터페이스를 구현하여, Java 파일을 통한 클래스 정의 없이 간단하게 Operator 인터페이스를 사용할 수 있다.
💡 추상 클래스(abstract class)도 위와 같은 방식으로 익명 구현 객체 생성이 가능하다.
익명 구현 객체의 한계점
오로지 하나의 인터페이스만 구현하여 객체를 생성할 수 있다.
자바에서 인터페이스는 다중 상속이 가능한 큰 이점과 본질을 가졌는데, 익명 클래스는 둘 이상의 인터페이스 상속이 불가능하다. 다중 상속을 받고 싶으면 일회용 용도일지라도 클래스를 따로 정의하여 사용해야 한다.
람다(Lambda) 표현식
람다(Lambda) 표현식이란?
Java8 이후부터는 람다 표현식이 가능하게 됐다. 람다식이란 메서드를 하나의 식으로 표현한 것이다.
함수를 람다식으로 표현하면 메소드의 이름이 필요 없기 때문에, 람다식은 익명 함수(Anonymous Function) 의 한 종류라고 볼 수 있다.
기본 람다 표현식은 3 부분으로 구성되어 있다.
매개 변수 목록 | 화살표 토큰 | 처리 식 |
(int x, int y) | -> | x + y |
좌측에는 넘겨지는 매개 변수들의 타입이 선언되고, 중간에는 화살표 연산자, 가장 우측에는 리턴되는 값을 표시한다.
익명 클래스와 람다 표현식
// 익명 클래스
int result = calculator.result(new Operate() {
@Override
public int operate(int a, int b) {
return a - b;
}
});
// 람다 표현식
int result = calculator.result((a, b) -> a - b);
상단의 익명 클래스를 아래와 같은 람다 표현식으로 변경할 수 있다.
람다 표현식의 제한
1. 인터페이스여야 한다.
2. 인터페이스에는 하나의 추상 메서드만 선언되어야 한다.
public interface Operate {
// 오버라이드 해야 할 메서드가 두 개인 경우
// 람다 표현식이 불가능하다
int operate(int a, int b);
void print();
}
Operate operate = new Operate() {
public int operate(int a, int b) {
return a + b;
}
public void print() {
System.out.println("출력");
}
};
오버라이드 해야 할 추상 메서드가 두 개 이상인 경우는 람다 표현식을 사용할 수 없다. 람다는 하나의 행위만을 사용한다고 가정하기 때문이다.
public interface Operate {
// 추상 메서드가 하나이다
int operate(int a, int b);
// default 메서드는 추상 메서드에 포함되지 않는다
default void print() {
System.out.println("출력");
}
}
Operate operate = (a, b) -> {
print();
return a + b;
};
그러나 추상 메서드가 아닌 default 메서드가 포함된 경우는 람다 표현식이 가능하다.