lambda(람다) 사용법
책/Effective Java

lambda(람다) 사용법

728x90
반응형
자바 8에서의 추가 
- 함수형 인터페이스 , 람다, 메서드 참조 : 함수 객체를 더욱 쉽게 만들기 
- 스트림 API : 데이터 원소의 시퀀스 처리를 라이브러리 차원에서 지원 

 

1997년 이전 : 기존에는 자바에서 함수 타입을 표현하기 위해 추상 메서드를 하나만 담은 인터페이스(추상 클래스)를 사용 

  • 이러한 인터페이스의 인스턴스 : 함수 객체(function object)라고 하여, 특정 함수나 동작을 나타내는 데 썼다. 

1997년 이후 :  JDK 1.1 등장과 함께 함수 객체 생성(표현) 방법 - 익명 클래스(아이템 24)를 통해서 충분히 표현했다. 

 

//익명 클래스의 인스턴스를 통해 함수 객체 사용하는 방법 

Collection.sort(words, new Comparator<String>() { //Comparator - 정렬 담당 추상 전략 
  public int compare(String s1, String s2){ //문자열을 정렬하는 방식을 구체화 
    return Integer.compare(s1.length(), s2.length());
  }
});

먼저 얘기하자면, 익명클래스를 통해 함수 객체를 사용하는 방법은 박물관에나 어울리는 구닥다리 방식이 되었다. 

위 코드는 Comparator 함수 객체를 익명클래스로 구현하여 sort function의 파라미터로 넘겨주는 코드이다. 

 

strategy 패턴처럼 함수 객체를 사용하는 과거 객체 지향 디자인 패턴에는 익명 클래스를 통해서 충분했다. 

하지만 익명 클래스 방식은 코드가 길기 때문에 함수형 프로그래밍으로는 적합하지 않았다. 

FP(함수형 프로그래밍) -
순수 함수(pure functions) 작성과 공유 상태(shared state), 변경 가능한 데이터(mutable date)와 side-effects 피하기로 소프트웨어를 구축하는 프로세스이다. 또 한, 함수형 프로그래밍은 명령적이라기보단 선언적이고 어플리케이션의 상태의 흐름이 순수 함수를 통해 흐른다. 이는 어플리케이션의 상태가 공유되고, 객체의 메소드와 함께 배치되는 객체 지향 프로그래밍과는 대조적이다.

 

자바 8에서는 추상 메서드 하나 짜리 인터페이스는 특별한 취급을 받게 된다. 

바로 lambda Expression 이 가능해졌다. 



Collection.sort(words, (s1,s2) -> Integer.compare(s1.length(), s2.length()));

// 자바 8에서 추가된 List 인터페이스 sort 메서드 사용한 케이스 
words.sort(comparingInt(String::length));
  • 각 매개변수, 반환값 등의 타입에 대한 언급이 없다. 
    • 즉 String 인자를 받아 int로 반환하는 함수형 인터페이스에 대한 타입 추론은 개발자가 명시하지 않아도, 컴파일러가 대신 해주기 떄문에 간결한 코드로 작성이 가능해진다. 
      • 이 때 컴파일러는 타입 추론은 대부분 제네릭을 통해서 이뤄진다. 
      • 만약 상황에 따라 컴파일러가 타입을 결정하지 못할 때는 개발자가 직접 타입을 명시해 주어야한다. 
    • 따라서 타입을 명시해야할 필요가 있을 때를 제외하고는, 람다의 모든 매개변수 타입을 생략하여 코드를 더욱 간결하게 하자 

 

# 열거형(Enum) 타입에서의 Lambda 사용 

  • 1. 상수별 클래스 몸체와 데이터를 사용한 열거 타입 
//이전 방법
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 operator;

    Operation(String symbol, DoubleBinaryOperator operator) {
        this.symbol = symbol;
        this.operator = operator;
    }

    @Override
    public String toString() {
        return symbol;
    }

    public double apply(double x, double y) {
        return operator.applyAsDouble(x, y);
    }
}
  • function body가 아주 간결해진 코드이다. 

# 람다 기반 Operation 열거 타입에서 상수별 클래스 몸체를 사용해야할 상황 

  1. lambda는 이름이 없기 때문에 문서화하기 어렵다. 따라서 코드 자체로 표현이 안될 때는 람다를 쓰지 말자 

  2. 열거 타입 생성자에 넘겨지는 파라미터의 타입도 컴파일타임에 추론이 이뤄진다. 
    따라서 열거 타입 생성자 안의 람다는 열거타입의 인스턴스 멤버에 접근할 수 없다.(인스턴스 생성은 런타임에 된다.)
    -> 자세한 설명은 : https://javabom.tistory.com/66 에 설명되어있다. 

# 람다가 대체할 수 없는 상황 

  1. 추상클래스의 인스턴스를 만들 때는 람다 사용이 불가능하다. (익명클래스 사용)
  2. 추상메서드가 여러개인 인터페이스의 인스턴스도 람다로 표현 불가능 
  3. 람다의 this는 바깥 인스턴스를 가리킨다. (람다는 자신을 참조할 수 없다) 
    반대로, 익명 클래스의 this는 익명 클래스 - 인스턴스 자신을 가리킨다.
    따라서 함수 객체가 자신을 참조해야한다면 반드시 익명 클래스를 사용하자 

# 유의 사항 

  • 직렬화가 필요한 경우에는 람다와 익명클래스를 삼가하도록 하자 
    • vm 마다 직렬화 형태가 다르다. 
  • 직렬화 해야만 하는 함수 객체는 정적 중첩 클래스의 인스턴스로 구현하자 

public class FruitCollection {
    private final FruitComparator comparator;
        ....

    public FruitCollection(Comparator<Fruit> comparator) {
        this.comparator = comparator;
    }

    static class FruitComparator implements Comparator<Fruit>, Serializable {
        private static final long serialVersionUID = 1;

        @Override
        public int compare(Fruit o1, Fruit o2) {...}
    }
} //과일들을 정렬하여 저장하는 컬렉션이 있다면, 아래와 같이 중첩 정적 클래스로 Comparator를 직렬화하여 선언 및 구현
정리 : 
익명 클래스는 (함수 인터페이스강 아닌) 타입의 인스턴스를 만들 때만 사용하자 
람다는 작은 함수 객체를 아주 쉽게 표현할 수 있어 FP의 시작을 열었다. 
728x90
반응형