[이펙티브 자바 아이템42] 익명 클래스보다는 람다를 사용하라

박상준·2024년 6월 19일
1

이펙티브 자바

목록 보기
13/16

익명 클래스의 단점

  • 과거 자바 8 등장 전에는 함수 객체 생성시 주로 익명 클래스를 사용함.
    • 익명 클래스는 이름이 없는 클래스로
    • 생성자로 객체 생성 후 객체 안의 public 메서드를 재정의가 가능한 클래스이다.

예시

Collections.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()));
  • 매개변수 타입 명시가 불필요
  • 컴파일러가 문맥을 통해 타입 추론
  • 코드의 길이 DOWN
    • 가독성 증대

더 간결하게 람다식 사용

Collections.sort(words, Comparator.comparingInt(String::length));
  • 비교자 생성 메서드를 통해 코드를 더욱 간결

함수형 인터페이스

  • 자바 8부터 추상메서드가 하나뿐인 인터페이스는 함수형 인터페이스로 취급
  • 람다식으로 인스턴스 생성가능

전략패턴과 람다

  • 과거에는 전략 패턴을 구현하는 경우 익명 클래스를 사용
  • 람다를 사용하는 경우 더 간결하게 작성이 가능함.

열거 타입 ex

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;
        }
        
        public double apply(double x, double y) {
            return op.applyAsDouble(x, y);
        }
    }
  • DoubleBinaryOperator → 함수형 인터페이스임.
    • double 타입 인수 2개를 받아 double 반환

람다의 단점

  • 가독성
    • 코드가 만약 길어진다면 람다는 가독성을 오히려 저해
  • 문서화가 안됨
    • 이름도 없고 문서화도 힘듬.
    • 코드의 동작이 명확치 않으면 익명클래스가 나음
  • 인스턴스 멤버에 접근 불가능
    • 열거 타입 생성자 안에서 인스턴스 멤버에 접근이 불가능함
  • 자기 참조가 불가능하다
    • 람다의 this 는 바깥 인스턴스를 가리킴
    • 함수 객체가 자신을 참조하는 경우 익명 클래스 사용

람다의 this 는 바깥 인스턴스를 가리킨다?

class OuterClass {
    Runnable r = () -> {
        System.out.println(this); // OuterClass의 인스턴스를 가리킴
    };
}
  • 익명 클래스 내부에서 this 는 익명 클래스의 인스턴스를 참조함.
    • 익명 클래스 자체의 인스턴스를 가리킴
      class OuterClass {
          Runnable r = new Runnable() {
              @Override
              public void run() {
                  System.out.println(this); // 익명 클래스의 인스턴스를 가리킴 (Runnable)
              }
          };
      }
      • 만약 Runnable 이라는 익명 클래스의 인스턴스를 가리킬 필요가 있는 경우 람다식을 사용하는 건 불가능하다.
  • 왜 그러냐?
    • 람다의 설계 방식의 문제임
    • 람다는 본질적으로 익명 함수
      • 별도의 새로운 클래스나 인스턴스를 생성하지 않고, 실행되는 경우 외부 환경(스코프)을 그대로 유지한다.
      • 람다 내부에서 this 를 사용하는 경우, 람다가 정의된 외부 클래스의 인스턴스를 참조.
      • 애초에 새로운 인스턴스를 생성하지 않는데 this 가 참조할 수 있겠는가

익명 클래스 필요성

  • 타입 인스턴스의 생성
    • 함수형 인터페이스가 아닌 타입의 인스턴스를 익명 클래스가 필요함.
  • 직렬화
    • 람다와 익명 클래스의 직렬화 형태가 달라서, 직렬화가 필요한 경우 private 정적 중첩 클래스 를 사용하는 게 좋음
      • private 정적 중첩 클래스
        public class OuterClass {
            private static class LengthComparator implements Comparator<String>, Serializable {
                private static final long serialVersionUID = 1L;
        
                @Override
                public int compare(String s1, String s2) {
                    return Integer.compare(s1.length(), s2.length());
                }
            }
        
            public static Comparator<String> getLengthComparator() {
                return new LengthComparator();
            }
        
            public static void main(String[] args) {
                Comparator<String> comparator = OuterClass.getLengthComparator();
                int result = comparator.compare("apple", "banana");
                System.out.println(result);  // Output: -1
            }
        }
        • 요런식으로 클래스 내부에 정적 클래스를 두어 OuterClas s와 별도의 직렬화 정책을 두는 것을 말함.
profile
이전 블로그 : https://oth3410.tistory.com/

0개의 댓글