요약

람다를 사용하면 좋을 때와 좋지 않을 때를 구별해 잘 사용한다면 효율적으로 구현할 수 있다. 그럼 언제가 람다를 사용하기 좋고, 안 좋을까?

람다를 사용해야할 때

람다는 코드가 깔끔해지고, 어떤 동작을 하는지가 명확하게 드러나기에 익명 클래스보다 사용이 권장된다.

 

람다를 사용하면 안될 때

  • 람다 코드가 길어지는 경우
  • 추상 클래스의 인스턴스를 만들어야하는 상황
  • 추상 메서드가 여러 개인 인터페이스 인스턴스를 만드는 상황
  • 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 익명 클래스

람다는 익명 클래스보다 적은 코드로 깔끔하고, 명확하게 코드를 작성할 수 있게 해주지만 다음과 같은 경우에는 람다보단 익명 클래스를 사용하는 것이 더 나을 수 있다.

  1. 추상 클래스의 인스턴스를 만들 때
  2. 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때
  3. 람다에서 this는 바깥 인스턴스를 가리키지만, 익명 클래스의 this는 익명 클래스 인스턴스 자신을 가리킨다

주의🚫 람다와 익명 클래스 인스턴스를 직렬화하는 일은 되도록이면 하지 말아야 한다. JVM 별로 직렬화 방식이 다르기 때문이다.

+ Recent posts