JAVA/Effective Java

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

꾸준함. 2024. 3. 2. 01:04

예전 자바에서 함수 타입을 표현하기 위해 추상 메서드를 하나만 담는 인터페이스를 사용했으며 해당 인터페이스의 인스턴스는 특정 함수 혹은 동작을 나타나내는 사용 했습니다.

JDK 1.1 등장 이후 이러한 함수 객체를 만드는 주요 수단이 아이템 24에서 간단히 다룬 익명 클래스였습니다.

 

익명 클래스

아이템 24에서 다룬 내용을 복습하자면 다음과 같습니다.

  • 익명 클래스는 바깥 클래스의 멤버가 아님 
  • 멤버와 달리 쓰이는 시점과 동시에 인스턴스가 생성됨 
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조 가능 
  • 정적 팩토리 메서드를 구현할 때 익명 클래스 사용 
  • 자바에서 람다를 사용하기 전에 주로 사용되었으며 람다가 지원되는 자바 8+ 버전부터는 거의 사용되지 않음 
    • 람다 표현식은 단일 추상 메서드(SAM, Single Abstract Method)를 가진 인터페이스의 구현에 사용 
    • 메서드가 많은 경우에는 해당 인터페이스가 람다로 적합하지 않을 수 있음

 

주로 정렬을 위한 비교 함수로 익명 클래스를 사용합니다.

 

 

 

람다

앞서 언급한 익명 클래스 방식은 다양한 동작을 구현할 수 있지만 코드가 너무 길어 함수형 프로그래밍에 적합하지 않다고 판단되어 자바 8+ 버전부터는 람다 표현식이 소개되었습니다.

Java에서는 함수를 직접적으로 표현할 수 있는 타입이 없어서, 기존에는 추상 메서드가 하나인 인터페이스를 활용하여 함수를 표현하는 방법을 고려했습니다.

이 아이디어를 기반으로 Java 8에서는 함수형 프로그래밍을 지원하기 위해 "함수형 인터페이스"라는 개념이 도입되었습니다.

함수형 인터페이스를 사용하면 람다 표현식을 쉽게 작성할 수 있게 되었으며, 이를 통해 코드의 간결성과 가독성을 향상할 수 있습니다.

람다 표현식은 함수나 익명 클래스와 개념은 비슷하지만 코드가 훨씬 간결한 것을 확인할 수 있습니다.

정렬을 위한 비교 함수로 람다 표현식을 사용하면 다음과 같습니다.

 

 

 

코드 부연 설명

  • 람다의 타입은 Comparator<String>, s1, s2의 타입은 String, 반환값의 타입은 int지만 코드에서는 이에 대한 언급이 없음
    • 개발자가 별도로 명시하지 않아도 컴파일러가 문맥을 살펴보며 타입을 추론하기 때문
    • 람다 표현식의 장점인 코드 간결성을 위해 타입을 명시해야 코드가 더 명확할 때만 제외하고는 람다의 모든 매개변수 타입은 생략하는 것을 권장
    • 컴파일러가 타입을 추론할 수 없어 "cannot infer type arguments for" 메시지와 함께 컴파일 에러를 발생시킬 때만 타입을 명시하면 됨
    • 아주 가끔 반환값이나 람다식 전체를 형변환해야 할 때도 있지만 아주 드문 케이스
  • 컴파일러가 타입 추론할 때 필요한 정보 대부분을 제네릭에서 얻기 때문에 람다를 사용할 때는 제네릭을 사용하는 것을 권장
    • 애초에 아이템 26에서 다루었다시피 로 타입 사용은 절대 금물

 

익명 클래스 vs 람다

앞서 언급했다시피 익명 클래스는 자바에서 람다를 사용하기 전에 주로 사용되었으며 람다가 지원되는 자바 8+ 버전부터는 거의 사용되지 않습니다.

하지만 특정 상황에서는 람다 대신 익명 클래스를 사용해야 하는 경우도 있습니다.

 

익명 클래스를 사용해야 하는 케이스

 

1. 메서드가 복잡할 때

람다는 이름이 없고 문서화를 못하기 때문에 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다가 적합하지 않을 수 있습니다.

람다는 한 줄일 때가 가장 좋고 가독성을 고려해 최대 세 줄 안에 끝내는 것을 권장합니다.

 

2. 추상 클래스의 인스턴스를 만들 때

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

비슷하게 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있습니다.

  • 추상 클래스는 단일 추상 메서드(SAM)를 가지고 있어야 람다 표현식으로 대체될 수 있음
  • 만약 추상 클래스가 단일 추상 메서드를 갖고 있지 않거나 여러 개의 추상 메서드를 가진 경우 람다 표현식을 사용할 수 없음

 

 

3. 자신을 참조해야 할 때

람다에서의 this 키워드는 바깥 인스턴스를 가리키기 때문에 자신을 참조할 수 없습니다.

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

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

 

 

람다 사용 시 주의 사항

람다도 익명 클래스처럼 직렬화 형태가 구현에 따라 다르게 동작할 수 있습니다.

따라서, 익명 클래스와 더불어 람다 또한 직렬화하는 일은 극히 삼가야 합니다.

Comparator와 같이 직렬화해야만 하는 함수 객체가 있다면 private 정적 중첩 클래스의 인스터를 사용하는 것을 권장합니다.

 

 

용어 설명: 함수형 인터페이스

함수형 인터페이스(Functional Interface)는 하나의 추상 메서드만을 가지는 특별한 종류의 인터페이스입니다.

자바에서는 함수형 인터페이스를 사용하여 람다 표현식을 지원하고, 이를 통해 함수형 프로그래밍을 간편하게 할 수 있게 되었습니다.

기본적으로 함수형 인터페이스는 다음과 같은 특성을 갖습니다:

  • 하나의 추상 메서드
    • 오직 하나의 추상 메서드만을 가지고 있어야 함
    • 그 외에는 default 메서드, static 메서드, Object 클래스의 메서드(toString, equals, hashCode 등) 등이 있을 수 있음

 

  • @FunctionalInterface 어노테이션
    • 함수형 인터페이스임을 명시하기 위해 @FunctionalInterface 어노테이션을 사용할 수 있음
    • 이는 인터페이스가 하나의 추상 메서드를 가지는 것을 보장
    • 그러나 어노테이션을 사용하지 않아도 함수형 인터페이스로 사용 가능

 

함수형 인터페이스를 사용하면 인터페이스를 구현하는 클래스나 람다 표현식을 통해 함수를 전달하거나 반환할 수 있습니다.

이를 통해 Java에서 함수형 프로그래밍의 장점을 활용할 수 있게 되었습니다.

자바 8+ 버전부터는 java.util.function 패키지에 여러 가지 기본 함수형 인터페이스들이 제공됩니다.

  • ex) Consumer, Supplier, Function, Predicate, etc.

 

참고

이펙티브 자바

반응형