Effective Java : 아이템 42. 익명 클래스보다는 람다를 사용하라.

    아이템 42. 익명 클래스보다는 람다를 사용하라 - 핵심정리

    • 함수 객체를 표현하기 위해 추상 메서드 하나만 가진 추상 클래스를 익명 클래스로 구현해, 익명 클래스 객체를 함수 객체로 표현함
    • 추상 메서드 하나만 가진 인터페이스는 함수형 인터페이스로 특별 대우를 받게 되었으며, 함수형 인터페이스는 람다식을 통해서도 구현할 수 있다
    • 익명 클래스 객체처럼 람다식은 '함수형 인터페이스'를 구현한 객체로 생성됨. 이 객체가 함수 객체로 표현됨. 
    • 람다식은 컴파일 타임에 제네릭을 이용해 타입을 추론함.
    • 람다식에는 타입을 명시해야 코드가 명확할 때를 제외하고는 타입을 명시하지 않음. 컴파일러가 타입을 알 수 없다고 에러를 내는 경우에만 타입을 명시해야함.
    • 익명 클래스 vs 람다식
      • 익명 클래스의 this는 익명 클래스 자기 자신을 가리킴. 람다 식의 this는 람다 식을 가지는 클래스를 가리킴. (서로 다름)
      • 람다식은 함수의 이름이 없기 때문에 가독성이 떨어짐. 식으로만 무슨 코드인지 알 수 없다면 익명 클래스를 사용하는게 좋음.

     


    함수 객체를 표현하기 위한 익명 클래스

    오래 전 자바는 함수 타입을 표현하기 위해서 추상 메서드를 하나만 담은 객체로 표현했다. 그리고 이걸 구현하는 수단으로 아래처럼 '익명 클래스'가 사용되었다. 그런데 이 방식은 다음 이유 때문에 자바가 함수를 표현할 때 적합하지 않았다. 

    • 익명 클래스는 코드가 너무 길다
    Collections.sort(List.of("A", "B"), new Comparator<>() {
        @Override
        public int compare(String o1, String o2) {
            return Integer.compare(o1.length(), o2.length());
        }
    });

     


    함수형 인터페이스 : 추상 메서드가 하나 짜리 인터페이스.

    추상 메서드가 하나짜리 인터페이스는 자바에서 함수를 표현하기 위해 사용되어왔다. 이 특별함을 인정받아 '함수형 인터페이스'라고 불리게 되었고, 자바8부터 함수형 인터페이스를 간결히 지원하기 위해 '람다식'이 도입되었다.

    Collections.sort(List.of("A", "B"),
            (s1, s2) -> Integer.compare(s1.length(), s2.length()));

    이전에는 함수 객체를 표현하기 위해 '익명 클래스' 객체를 생성해서 전달했었다. 하지만 지금부터는 위처럼 함수 구현부분만 선언한다. 람다식은 함수형 인터페이스의 인스턴스를 생성해주는 것이기 때문에 '익명 클래스' 객체를 생성해서 전달하는 것과 유사하다. 

     


    람다는 생성자에 넘겨 간소화 할 수도 있음. 

    람다는 함수형 인터페이스를 구현한 객체라고 했다. 따라서 생성자를 이용해 객체를 생성할 때, 람다 객체를 전달할 수 있다. 전달된 람다 객체를 이용해 deligate를 하면 함수형 인터페이스를 이용해 함수를 실행할 수 있다. 아래의 예제를 살펴보자. 

    아래 예제는 enum이 추상 메서드를 가지고 있고, 각 인스턴스를 생성할 때 추상 메서드를 하나씩 구현한 경우를 나타낸다. 

    public enum OperationEnumWithLambdaBad {
    
        PLUS("+"){ @Override public double apply(double x, double y) {return x + y;}},
        MINUS("-"){ @Override public double apply(double x, double y) {return x - y;}},
        TIME("*"){ @Override public double apply(double x, double y) {return x * y;}},
        DIVIDE("/"){ @Override public double apply(double x, double y) {return x / y;}};
    
        OperationEnumWithLambdaBad(String symbol) {
            this.symbol = symbol;
        }
    
        private final String symbol;
    
        public abstract double apply(double x, double y);
    }

    위 enum 클래스는 추상 클래스 대신에 함수형 인터페이스 객체를 전달하고 Deligate 하도록 하면 좀 더 깔끔하게 작성할 수 있다. 아래에서는 다음과 같이 풀어냈다. 

    • OperationEnumWithLambdaGood 열거 타입은 DoubleBinaryOperator라는 함수형 인터페이스를 가짐. 
    • 각 인스턴스를 생성할 때, 함수형 인터페이스를 받아야 하는데 이 때 함수형 인터페이스에 람다식을 전달함. 
    • 함수형 인터페이스에 apply() 메서드를 호출해 deligate 함. 
    public enum OperationEnumWithLambdaGood {
    
        PLUS("+", (x, y) -> x + y),
        MINUS("-", (x, y) -> x - y),
        TIME("*", (x, y) -> x * y),
        DIVIDE("/", (x, y) -> x / y);
    
        OperationEnumWithLambdaGood(String symbol, DoubleBinaryOperator op) {
            this.symbol = symbol;
            this.op = op;
        }
    
        private final String symbol;
        private final DoubleBinaryOperator op;
    
        public double apply(double x, double y) {
            return op.applyAsDouble(x, y);
        }
    }

     


    람다  vs 익명 클래스, 언제 써야할까?

    람다 클래스의 단점은 다음과 같다.

    • 람다 식은 이름이 없음. 람다 클래스는 문서화 할 수 없음. 
    • 람다 식은 함수형 인터페이스의 구현체임. 따라서 멤버 필드를 이용할 수 없음.  (인터페이스처럼)

    이런 단점을 가지고 있다. 따라서 다음 경우에는 람다식 보다 익명 클래스를 쓰는 것이 나은 선택일 수 있다. 

    • 코드만으로 뜻을 유추할 수 없는 경우 (람다 식은 함수 이름이 없으므로 가독성 문제) 
    • 열거 타입의 생성자에 전달된 람다가 열거 타입의 멤버를 참조해야 하는 경우 (람다는 컴파일 시점 생성, 

    또 한 가지 주의해야 할 점은 다음과 같다. 

    익명 클래스의 this는 익명 클래스를 가리지만, 람다식의 this는 람다식을 가진 클래스를 의미한다. 따라서 함수 내에서 자기 자신을 지칭해야 하는 경우라면 반드시 익명 클래스를 사용해야 한다. 

    댓글

    Designed by JB FACTORY