익명 클래스의 단점
- 과거 자바 8 등장 전에는 함수 객체 생성시 주로 익명 클래스를 사용함.
- 익명 클래스는 이름이 없는 클래스로
- 생성자로 객체 생성 후 객체 안의 public 메서드를 재정의가 가능한 클래스이다.
예시
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
- 코드가 상당히 장황하다.
- 자바 8 부터 람다식으로 도입해 코드 간결 + 가독성 증대
람다식
Collections.sort(words,
(s1, s2) -> Integer.compare(s1.length(), s2.length()));
- 매개변수 타입 명시가 불필요
- 컴파일러가 문맥을 통해 타입 추론
- 코드의 길이 DOWN
더 간결하게 람다식 사용
Collections.sort(words, Comparator.comparingInt(String::length));
함수형 인터페이스
- 자바 8부터 추상메서드가 하나뿐인 인터페이스는 함수형 인터페이스로 취급
- 람다식으로 인스턴스 생성가능
전략패턴과 람다
- 과거에는 전략 패턴을 구현하는 경우 익명 클래스를 사용
- 람다를 사용하는 경우 더 간결하게 작성이 가능함.
열거 타입 ex
public enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y);
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
- DoubleBinaryOperator → 함수형 인터페이스임.
- double 타입 인수 2개를 받아 double 반환
람다의 단점
- 가독성
- 코드가 만약 길어진다면 람다는 가독성을 오히려 저해
- 문서화가 안됨
- 이름도 없고 문서화도 힘듬.
- 코드의 동작이 명확치 않으면 익명클래스가 나음
- 인스턴스 멤버에 접근 불가능
- 열거 타입 생성자 안에서 인스턴스 멤버에 접근이 불가능함
- 자기 참조가 불가능하다
- 람다의
this
는 바깥 인스턴스를 가리킴
- 함수 객체가 자신을 참조하는 경우 익명 클래스 사용
람다의 this
는 바깥 인스턴스를 가리킨다?
class OuterClass {
Runnable r = () -> {
System.out.println(this);
};
}
- 익명 클래스 내부에서
this
는 익명 클래스의 인스턴스를 참조함.
- 왜 그러냐?
- 람다의 설계 방식의 문제임
- 람다는 본질적으로
익명 함수
별도의 새로운 클래스나 인스턴스를 생성하지 않고,
실행되는 경우 외부 환경(스코프)을 그대로 유지한다.
- 람다 내부에서
this
를 사용하는 경우, 람다가 정의된 외부 클래스의 인스턴스를 참조.
- 애초에 새로운 인스턴스를 생성하지 않는데
this
가 참조할 수 있겠는가
익명 클래스 필요성
- 타입 인스턴스의 생성
- 함수형 인터페이스가 아닌 타입의 인스턴스를 익명 클래스가 필요함.
- 직렬화
- 람다와 익명 클래스의 직렬화 형태가 달라서, 직렬화가 필요한 경우
private 정적 중첩 클래스
를 사용하는 게 좋음
- private 정적 중첩 클래스
public class OuterClass {
private static class LengthComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
public static Comparator<String> getLengthComparator() {
return new LengthComparator();
}
public static void main(String[] args) {
Comparator<String> comparator = OuterClass.getLengthComparator();
int result = comparator.compare("apple", "banana");
System.out.println(result);
}
}
- 요런식으로 클래스 내부에 정적 클래스를 두어 OuterClas s와 별도의 직렬화 정책을 두는 것을 말함.