Language/Java
[effective java] 아이템 42. 익명 클래스보다는 람다를 사용하라.
JOYERIM
2023. 7. 2. 23:09
핵심 내용
자바 8부터 도입된 람다 함수는 기존의 익명 클래스보다 훨씬 간결하다.
익명 클래스의 경우, 인스턴스가 필요할 경우 사용하자.
직렬화해야 할 경우에는 private 정적 중첩 클래스의 인스턴스를 사용하자. (익명 클래스와 람다 함수 모두)
낡은 방법
: 익명 클래스 방식
Collection.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()));
람다식 방식은 훨씬 간결하다. 위 코드를 살펴보면, 매개변수(s1, s2), 반환값의 타입은 (Comparator<String>), String, int지만 지정하지 않아도 정상동작한다. 컴파일러가 타입을 추론한 것으로, 상황에 따라 프로그래머가 직접 명시해야 한다.
타입 추론 규칙은 매우 복잡하므로 명확하지 않을 경우에만 타입을 지정할 경우나, 컴파일러가 오류를 낼 경우에만 명시하면 된다.
컴파일러가 타입을 추론할 때 필요한 타입 정보 대부분을 제네릭에서 얻는다.
즉, 아래 규칙을 더욱 명심해서 사용하라.
- 제네릭의 raw 타입을 쓰지 마라(아이템 26)
- 제네릭을 사용하라(아이템 29)
- 제네릭 메소드를 쓰라(아이템 30)
Collections.sort(words, comparingInt(String::length));
위 코드처럼 비교자 생성 메소드를 사용하면 더 간결해진다.
words.sort(comparingInt(String::length));
자바 8 때 List 인터페이스에 추가된 sort 메소드를 사용하면 더 간결해진다.
예시 1
: 열거 타입
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
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 double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n",
x, op, y, op.apply(x, y));
}
}
람다로 구현하여 훨씬 간결하게 작성할 수 있다.
람다식 방식 관련 주의해야 할 점
람다는 이름이 없고 문서화도 못 한다.
따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다.
열거 타입 생성자가 실행되는 시점에는 열거 타입 인스턴스들은 아직 완전히 생성되지 않는다. 따라서 생성자 또는 초기화 블록에서는 인스턴스 메소드나 필드를 사용할 수 없다. 그러나 상수별 클래스 몸체는 열거 타입 상수가 이미 생성된 후에 인스턴스 메소드가 실행되므로 인스턴스 필드나 메소드를 사용할 수 있다. (예제 1 Operation 참고)
추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 사용해야 한다.
또한, 추상 메소드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 사용해야 한다.
람다는 자신을 참조할 수 없다.
람다에서의 this 키워드는 바깥 인스턴스를 가리킨다. (<-> 익명 클래스에서의 this는 익명 클래스의 인스턴스 자신을 가리킨다.)
즉, 자신을 참조해야 한다면 반드시 익명 클래스를 사용해야 한다.
람다와 익명클래스의 인스턴스는 직렬화하지 말아야 한다. (구현별로 달라질 수 있으므로 !!)
직렬화해야만 하는 함수 객체(ex. Comparator)가 있다면 private 정적 중첩 클래스(아이템 24)의 인스턴스를 사용하자.
import java.io.Serializable;
import java.util.Comparator;
public class Person {
private String name;
// ... 기타 필드와 메서드 ...
public String getName() {
return name;
}
// 'name' 기준으로 비교하는 Comparator, 직렬화 가능
public static final Comparator<Person> NAME_ORDER =
new NameOrderComparator();
private static class NameOrderComparator
implements Comparator<Person>, Serializable {
@Override
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name);
}
}
}