Java/Effective Java 3E

[이펙티브자바 3판] ITEM42. 익명 클래스보다는 람다를 사용하라

잭피 2020. 12. 1. 21:54

이번장의 핵심은...

익명 클래스는 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때만 사용하라


함수 객체(function object)

추상 메서드 하나만 담은 인터페이스(Functional Interface)의 인스턴스

예제) 문자열을 길이순으로 정렬

List<String> words = Arrays.asList(args);

// 코드 42-1 익명 클래스의 인스턴스를 함수 객체로 사용 - 낡은 기법이다! (254쪽)
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

→ 익명 클래스 방식은 코드가 너무 길기 때문에 자바는 함수형 프로그래밍에 적합하지 않았습니다

 

자바 8부터 추상 메서드 하나짜리 인터페이스는 특별한 의미를 인정받았습니다 (함수형 인터페이스)

함수형 인터페이스들의 인스턴스르를 람다식을 사용해 만들 수 있다

위 코드를 람다로 바꾸면,

// 코드 42-2 람다식을 함수 객체로 사용 - 익명 클래스 대체 (255쪽)
Collections.sort(words, (s1,s2)-> Integer.compare(s1.length(), s2.length())

 

람다식에서 타입은 컴파일러가 문맥을 살펴 추론해준다 → 타입 추론 규칙은 매우 복잡하므로 몰라도 된다

 

타입을 명시해야 코드가 더 명확할 때만 제외하고는, 람다의 모든 매개변수 타입은 생략하자
또한, 컴파일러가 타입을 알 수 없다는 오류를 낼 때만 해당 타입을 명시하면 된다

 

람다 자리에 비교자 생성 메서드를 사용하면 더욱 간결해집니다

Collections.sort(words, comparingInt(String::length));

 

List 인터페이스에 추가된 sort를 이용하면 더욱 짧아집니다

words.sort(comparingInt(String::length));

 

람다를 언어 차원에서 지원하면서 함수 객체를 더욱 실용적으로 사용할 수 있다

예시) Operation 열거 타입 

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);
    }
}
// DoubleBinaryOperator - 함수 인터페이스중 하나로,
// Double 타입 인수 2개를 받아 Double 타입 결과를 돌려준다
@FunctionalInterface
public interface DoubleBinaryOperator {
    double applyAsDouble(double left, double right);
}

 

람다 기반 Operation 열거 타입을 보면 상수별 클래스 몸체는 더 이상 사용할 필요가 없다?

-> NO

 

메서드, 클래스와 달리 람다는 이름이 없고, 문서화도 못합니다

따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 합니다

람다는 1줄일 때가 가장 BEST이고 길어도 3줄까지는 끝내자 (1~3줄)

 

상수별 클래스 몸체를 사용해야하는 상황

1. 상수별 동작을 단 몇 줄로 구현하기 어려울 때

2. 인스턴스 필드나 메서드를 사용해야만 하는 상황

→ 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없습니다

(인스턴스는 런타임에 만들어지기 때문입니다)

 

람다의 시대가 열리면서 익명 클래스가 설 자리가 크게 사라졌습니다

하지만, 아직 람다가 대체할 수 없는 곳이 있습니다

 

1. 람다는 함수형 인터페이스에서만 쓰입니다

예를 들어 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 써야 합니다

또한, 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있습니다

 

2. 람다는 자신을 참조할 수 없습니다

람다에서 this 키워드는 바깥 인스턴스를 가르킵니다

반면, 익명 클래스에서의 this는 익명 클래스의 인스턴스 자신을 가르킵니다

따라서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야합니다

 

직렬화

람다도 익명 클래스처럼 직렬화 형태가 구현별로 다를 수 있습니다

따라서 람다, 익명클래스의 인스턴스를 직렬화하는 일은 극히 삼가야 합니다

 

직렬화해야만 하는 함수 객체가 있다면 private 정적 중첩 클래스(Item 24)의 인스턴스를 사용하자

jackjeong.tistory.com/48?category=802500

 

[이펙티브자바 3판] ITEM24. 멤버 클래스는 되도록 static으로 만들라

이번장의 핵심은... 중첩 클래스에는 네 가지가 있으며, 각각의 쓰임이 다르다 메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기엔 너무 길다면 멤버 클래스로 만든다 멤버 클래스의 인스

jackjeong.tistory.com

 

 

 

이 글은 “이펙티브 자바 3판” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “shk3029@kakao.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.