과거에는 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스를 사용했다. 이런 인터페이스의 인스턴스를 함수 객체라고 해서, 특정 함수나 동작을 나타내는 데 사용했다. JDK1.1이 등장하면서 이 함수 객체를 만드는 주요 수단은 익명 클래스가 되었다. 다음 코드를 예로 들어보자.
Collection.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()));
람다의 매개변수에는 타입에 대한 언급이 없다. 이는 컴파일러가 타입 추론 규칙에 의해 타입을 추론해줬기 때문이다. 타입 명시 규칙은 너무 복잡하기 때문에 타입을 명시해야 코드가 더 명확해지는 경우에만 제외하고는 람다의 매개변수 타입을 생략하도록 하자.
컴파일러가 타입을 추론할 때 필요한 타입 정보 대부분을 제네릭에서 얻기 때문에 람다는 제네릭을 사용할 때 두배로 중요해진다. 우리가 타입 정보를 제공하지 않으면 컴파일러는 람다의 타입을 추론할 수 없게 되어 결국 우리가 일일이 명시해야 한다.
람다 자리에 비교자 생성 메서드를 사용한다면 이 코드를 더 간단하게 만들 수 있다.
Collections.sort(words, comparingInt(String::length));
더 나아가 자바8 때 List 인터페이스에 추가된 sort 메서드를 사용하면 더욱 짧아진다.
words.sort(comapringInt(String::length));
람다는 메서드나 클래스와는 달리, 이름이 없고 문서화도 하지 못한다. 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다. 람다는 한 줄일 때 가장 좋고, 길어도 세 줄을 넘기면 안된다. 가독성이 나빠지기 때문이다.
열거 타입 생성자에 주어지는 인수들의 타입들은 컴파일 타임에 추론된다. 따라서 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없다(인스턴스는 런타임에 만들어지기 떄문이다). 따라서, 상수별 동작을 단 몇줄로 구현하기 어렵거나 인스턴스 필드나 메서드를 사용해야 하는 경우에는 상수별 클래스 몸체를 사용해야 한다.
람다는 함수형 인터페이스에서만 사용된다. 추상 클래스의 인스턴스를 만들 때는 람다를 사용할 수 없기 때문에 익명 클래스를 사용해야 한다. 비슷하게, 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있다.
마지막으로, 람다는 자신을 참조할 수 없다. 람다에서의 this
키워드는 바깥 인스턴스를 가리킨다. 반면 익명 클래스의 this
는 익명 클래스의 인스턴스 자신을 가리킨다. 그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야한다.
람다도 익명 클래스처럼 직렬화 형태가 구현별로 다를 수 있다. 따라서, 람다를 직렬화 하는 것은 극히 삼가해야 한다(익명 클래스의 인스턴스도 마찬가지다). 직렬화해야만 하는 함수 객체가 있다면(Comparator 처럼) private 중첩 클래스[아이템24]의 인스턴스를 사용하자.