ITEM 42
익명 클래스보다는 람다를 사용하라
자바 8 이전에는 다음과 같이 익명 클래스를 사용했다.
@Test
@DisplayName("자바8 이전에 사용하던 익명클래스")
void anonymousClass() {
List<String> word = new ArrayList<>(List.of("ab","abc","a"));
Collections.sort(word, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
});
}
하지만 자바 8 이후부터 인터페이스 내에 추상 메서드가 단 하나만 존재하면 람다식을 사용할 수 있게 되었다.
위에서 사용하던 익명 클래스 방식을 람다 표현식으로 다음과 같이 간결하게 표현할 수 있다.
Collections.sort(word,(s1,s2)->Integer.compare(s1.length(),s2.length()));
람다 표현식을 사용하게 되면 타입을 생략할 수 있는데
생략할 수 있는 이유는
컴파일러가 대신 인터페이스에 대한 타입 추론을 대신해주기 때문이다.
컴파일러가 타입 추론을 할 때 정보를 얻는 방법은 대부분 제네릭을 통해 얻는다.
만약 위의 예제에서 List <String> 이 아니라 로 타입인 List 였다면 컴파일 오류가 발생한다.
@Test
@DisplayName("로 타입 List 임으로 타입추론 불가(컴파일에러)")
void compileError(){
List word = new ArrayList(List.of("ab","abc","a"));
Collections.sort(word,(s1, s2) -> Integer.compare(s1.length(),s2.length()));
}
해당 코드에서 s1, s2의 타입 추론할 수 없음으로 String의 length()를 호출할 수 없다.
열거 타입에서의 람다 사용
상수마다 메서드의 행동을 다르게 해야 하는 경우
상수 별 메서드 구현(constant-specific method implementation)을 했었다.(Item34)
public enum CARD {
KAKAO("카카오"){
@Override
int point(int money) {
return money/100;
}
},SAMSUNG("삼성"){
@Override
int point(int money) {
return money*2/100;
}
},HYUNDAI("현대"){
@Override
int point(int money) {
return money*3/100;
}
};
private String name;
abstract int point(int money);
}
해당 방식으로 구현하는 것보다 함수 객체(람다)를 인스턴스 필드에 저장해서 보기 편한 코드로 변경해보자.
public enum CARD {
KAKAO("카카오",money->money/100),
SAMSUNG("삼성",money->money*2/100),
HYUNDAI("현대",money->money*3/100);
private final String name;
private final Function<Integer, Integer> op;
CARD(String name ,Function<Integer,Integer> op) {
this.name = name;
this.op =op;
}
public int point(int money){
return op.apply(money);
}
public String getName() {
return name;
}
}
람다 방식으로 구현하면 코드가 매우 깔끔해진다.
그렇다고 해서 상수 별 메서드 구현 방식을 쓰지 않는 것은 아니다.
상수 별 메서드 구현 방식을 사용하는 상황
-
람다는 이름이 없고 문서화를 하기 어렵다
-> 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 사용하지 말자.
-
열거 타입 생성자에 넘겨지는 인수들의 타입도 컴파일 타임에 추론된다.
-> 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없다.
람다가 대체할 수 없는 곳
-
추상 클래스의 인스턴스를 만들 때 람다 사용은 불가능하다.
-
인터페이스의 추상 메서드가 여러 개면 람다로 표현 불가능하다.
-
람다는 자신을 참조할 수 없다.
-> 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다.
public interface Foo {
int anyThing(int i);
}
public class Test {
private final int value = 20;
public Foo foo = new Foo() {
final int value = 10;
@Override
public int anyThing(int i) {
return i + this.value; //익명 클래스는 인스턴스 자신을 가리킨다.
}
};
public Foo foo2 = i -> i + this.value; //람다는 바깥 인스턴스를 가리킨다.
}
주의사항
람다도 익명 클래스처럼 직렬화 형태가 구현 별로(가상 머신 별로) 다를 수 있다.
따라서 람다를 직렬화 하는 일은 극히 삼가야 한다.(익명 클래스의 인스턴스도 마찬가지다)
직렬화를 해야 할 함수 객체가 있다면 private 중첩 클래스(아이템 24)의 인스턴스를 사용하자.
'Book Review > effective-java' 카테고리의 다른 글
[ITEM22] 인터페이스는 타입을 정의하는 용도로만 사용하라 (0) | 2021.02.20 |
---|---|
[ITEM21] 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) | 2021.02.18 |
[ITEM20] 추상 클래스보다는 인터페이스를 우선하라. (0) | 2021.02.16 |
[ITEM18] 상속보다는 컴포지션을 사용하라 (0) | 2021.02.14 |
[ITEM17]변경 가능성을 최소화하라 (0) | 2021.02.14 |