ITEM 42 "익명 클래스보다는 람다를 사용하라"
Java 8 이전에는 자바에서 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스를 사용했었다. 이런 인터페이스의 인스턴스를 함수 객체라고 하는데 다른 곳에서 절대 사용하지 않는 가벼운 함수 객체라면 함수 객체를 인자로 받는 공간에 바로 new 연산자를 통해 생성할 수 있었다.
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
이렇게 인터페이스 (위 예제에서는 Comparator 인터페이스) 를 바로 new 연산자를 통해 사용하고, 구체적인 전략은 익명 클래스 내부에 작성함으로써 전략 패턴의 장점도 사용할 수 있다. 하지만 매우 간단한 코드임에도 불구하고 매우 길다.
Java 8 부터는 추상 메서드 하나짜리 인터페이스는 람다식으로 간결하게 작성할 수 있다.
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
매개변수와 반환값에 대한 타입은 각각 String 과 Int 이지만 생략할 수 있다. 컴파일 타임 때 컴파일러가 맥락을 파악하고 자동으로 타입을 추론해주기 때문에 가능하다. 간혹 컴파일러가 타입을 추론하지 못해 컴파일 오류가 발생할 수도 있지만, 그럴 때 직접 프로그래머가 명시하면 된다. 타입을 명시해야 코드가 명확한 경우를 제외하고 람다의 모든 매개변수 타입은 생략하자.
제네릭에서 타입에 대한 정보를 얻을 수 있기 때문에 람다를 사용하는 함수들에서 제네릭을 적극적으로 사용하는 것을 권장한다. 로타입으로 구성했다면 컴파일 오류가 나서 람다 사용하는 부분에서 타입 변환을 해야한다.
메서드 참조를 사용하면 더 간결하게 할 수도 있다.
Collections.sort(words, comparingInt(String::length));
더 나아가 List 인터페이스에 추가된 sort 메서드를 이용하면 더 간결해진다.
words.sort(comparingInt(String::length));
아래와 같이, 열거 타입에서 상수 별 추상 메서드를 직접 구현하기 보다는 인스턴스 필드를 인자로 전달받아 간결하게 구현할 수도 있다. (람다로 구현하니 더 깔끔해진다)
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;
}
@Override
public String toString() { return symbol; }
public abstract double apply(double x, double y) {
return op.applyAsDouble(x, y);
};
}
위 코드에서 사용한 DoubleBinaryOperator 는 java.util.function 패키지가 제공하는 인터페이스 중 하나로 double 타입 인수 2개를 받아 double 타입 결과를 돌려주는 인터페이스이다.
간결하게 작성할 수 있다는 가장 큰 장점을 가지고 있는 람다도 사용 시 유의할 사항이 존재한다.
첫 번째, 람다는 이름이 없기 때문에 문서화하기가 곤란하다. 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아진다면 람다를 쓰지 말아야 한다.
두 번째, 인스턴스로 만들어서 재사용할 때 람다를 쓸 수 없다. 익명 클래스를 사용해야 한다. 람다는 태생적으로 한 개의 추상 메서드를 가진 추상 클래스이므로 추상 메서드가 여러 개인 인터페이스의 인스턴스로도 활용이 불가능하다.
세 번째, 익명 클래스에서 this 지시자를 통해 자기 자신의 인스턴스를 참조하는 것과 다르게 람다에서 this 지시자는 람다 바깥 인스턴스를 참조한다.
네 번째, 람다도 익명 클래스처럼 역직렬화/직렬화 형태가 가상머신 VM 마다 구현이 다를 수 있어 유의해야 한다. private 정적 중첩 클래스의 인스턴스를 사용하는 것을 권장한다.
"꼭 익명 클래스를 사용해야 하는 자리가 아니라면 익명 클래스보다 간결하게 작성할 수 있는 람다를 사용해보자"
'독후감 > Effective JAVA' 카테고리의 다른 글
[Effective JAVA] 44 "표준 함수형 인터페이스를 사용하라" (0) | 2024.03.03 |
---|---|
[Effective JAVA] 43 "람다보다는 메서드 참조를 사용하라" (0) | 2024.03.02 |
[Effective JAVA] 41 "정의하려는 것이 타입이라면 마커 인터페이스를 사용하라" (0) | 2023.11.06 |
[Effective JAVA] 40 "@Override 애너테이션을 일관되게 사용하라" (1) | 2023.10.29 |
[Effective JAVA] 39 "명명 패턴보다 애너테이션을 사용하라" (1) | 2023.10.24 |