요약
람다를 사용하면 좋을 때와 좋지 않을 때를 구별해 잘 사용한다면 효율적으로 구현할 수 있다. 그럼 언제가 람다를 사용하기 좋고, 안 좋을까?
람다를 사용해야할 때
람다는 코드가 깔끔해지고, 어떤 동작을 하는지가 명확하게 드러나기에 익명 클래스보다 사용이 권장된다.
람다를 사용하면 안될 때
- 람다 코드가 길어지는 경우
- 추상 클래스의 인스턴스를 만들어야하는 상황
- 추상 메서드가 여러 개인 인터페이스 인스턴스를 만드는 상황
- this를 사용해 자신을 참조해야하는 상황
함수 객체
함수 객체는 예전에는 자바에서 함수 타입을 표현할 때, 추상 메서드를 하나만 담은 인터페이스의 인스턴스를 의미한다.
익명 클래스로 함수 객체 구현
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
- Comparator 인터페이스가 정렬을 담당하는 추상 전략을 의미
- 문자열을 정렬하는 구체적인 전략을 익명 클래스로 구현
- 하지만 코드가 너무 길어서 자바에는 적합한 방식이 아니었다
람다식
람다식은 짧게는 람다로 불리며, 함수형 인터페이스(추상 메서드 하나짜리 인터페이스)의 인스턴스를 의미한다.
// case1
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
// case2
Collections.sort(words, comparingInt(String::length));
// case3
words.sort(comparingInt(String::length));
- 코드가 간단해짐
- 어떤 동작을 하는지가 명확하게 드러남
타입 추론
위 람다 코드를 보면 람다의 반환값 타입이 명시되어 있지 않다. 이는 컴파일러가 문맥 타입을 추론해줬기 때문이다. 대부분의 경우 컴파일러가 타입 추론을 할 수 있지만, 안되는 경우에는 프로그래머가 직접 명시해야한다. 하지만 타입 추론은 복잡하기 때문에 타입을 명시해 명확하게 처리해야 할 때(컴파일러가 “타입을 알 수 없다”는 오류를 낼 때) 빼고는 람다의 모든 매개변수 타입은 생략하는 것을 권장한다.
추가로 컴파일러가 타입 추론을 용이하게 할 수 있도록 제네릭 사용을 권장한다.
람다 예시 - 열거 타입
람다를 이용하면 열거 타입의 인스턴스 필드를 이용하는 방식으로 상수별로 다르게 동작하는 코드를 쉽게 구현할 수 있다.
- 이전 버전 - 상수별 클래스 몸체
public enum Operation {
PLUS {
public double apply(double x, double y) { return x + y; }
}
MINUS {
....
}
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 enum Operation {
PLUS ("+", (x, y) -> x + y),
MINUS ("-", (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);
}
}
- 열거 타입 상수의 동작을 람다로 구현해 생성자에 넘기고
- 생성자는 해당 람다를 인스턴스 필드로 저장한다
- apply 메서드에서 필드에 저장된 람다를 호출하기만 하면 된다
→ 원래 버전보다 코드가 간결해지고, 깔끔해진다.
열거 타입에서 람다 장점
- 원래 버전보다 코드가 간결해지고, 깔끔해진다
열거 타입에서 람다 단점
- 람다는 이름이 없고, 문서화도 못한다
- 코드 자체로 동작이 명확히 설명되지 않거나 코드가 길어지면 람다를 사용하면 안된다
- 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없다
- 인스턴스는 런타임에 만들어지기 때문
→ 상수별 동작을 단 몇 줄로 구현하기 어렵거나, 인스턴스 필드나 메서드를 사용해야만 하는 상황이라면 상수별 클래스 몸체를 사용해야한다.
람다 vs 익명 클래스
람다는 익명 클래스보다 적은 코드로 깔끔하고, 명확하게 코드를 작성할 수 있게 해주지만 다음과 같은 경우에는 람다보단 익명 클래스를 사용하는 것이 더 나을 수 있다.
- 추상 클래스의 인스턴스를 만들 때
- 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때
- 람다에서 this는 바깥 인스턴스를 가리키지만, 익명 클래스의 this는 익명 클래스 인스턴스 자신을 가리킨다
주의🚫 람다와 익명 클래스 인스턴스를 직렬화하는 일은 되도록이면 하지 말아야 한다. JVM 별로 직렬화 방식이 다르기 때문이다.
'Dev Language > EffectiveJava' 카테고리의 다른 글
[EffectiveJava] 스트림 병렬화는 주의해서 적용하라 (0) | 2025.03.03 |
---|---|
[EffectiveJava] 스트림에서는 부작용 없는 함수를 사용하라 (0) | 2025.02.23 |
[EffectiveJava] 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 (0) | 2025.02.09 |
[EffectiveJava]명명패턴 대신 애너테이션을 사용하라 (0) | 2025.01.31 |
[EffectiveJava]비트 필드 대신 EnumSet을 사용하라 (2) | 2025.01.25 |