자바 8부터 추상 메서드 하나짜리 인터페이스는 특별한 의미를 인정받아 특별한 대우를 받게 되었다.
함수형 인터페이스라 부르는 이 인터페이스들의 인스턴스를 람다식을 사용해 만들 수 있게 된 것이다.
람다는 함수나 익명 클래스와 개념은 비슷하지만 코드는 훨씬 간결하다.
아래 코드는 문자열의 길이를 기준으로 오름차순으로 정렬하는 코드이다.
총 네 가지의 방식으로 정렬이 가능한데, 2번부터 모던한 코드라고 소개해주고 있다.
(개인적으로 2번이 제일 무난한 것 같다)
public class Lambda {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
words.add("abcde");
words.add("abcd");
words.add("a");
words.add("ab");
words.add("abc");
// 1. 익명 클래스 이용
/*
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
*/
// 2. 람다식을 함수 객체로 사용
/*
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
*/
// 3. 비교자 생성 메서드
/*
Collections.sort(words, Comparator.comparingInt(String::length));
*/
// 4. Java 8부터 추가된 List 인터페이스의 sort 메서드
/*
words.sort(Comparator.comparingInt(String::length));
*/
for(String word : words) {
System.out.println("word : " + word);
}
}
}
아래 코드는 enum 객체에서 상수별 클래스 몸체를 구현한 코드이다.
// 상수별 클래스 몸체 구현
public 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),
DIVICE ("/", (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 abstract double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
위 코드는 함수 객체를 인스턴스 필드에 저장해 상수별 동작을 구현한 열거 타입이다.
람다 기반 Operation 열거 타입을 보면 상수별 클래스 몸체는 더 이상 사용할 이유가 없다고 느낄지 모르지만, 꼭 그렇지는 않다.
메서드나 클래스와 달리, 람다는 이름이 없고 문서화도 못 한다. 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 람다를 쓰지 말아야 한다.
세 줄을 넘어가면 가독성이 심각하게 나빠진다고 한다. 그럴 경우 람다를 쓰지 않는 방향 혹은 줄을 줄일 수 있도록 리팩토링 해야한다.
이처럼 람다의 시대가 열리면서 익명 클래스는 설 자리가 크게 좁아진 게 사실이다. 하지만 람다로 대체할 수 없는 곳이 있다.
1. 람다는 함수형 인터페이스에서만 쓰인다. 예컨대 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없으니, 익명 클래스를 써야 한다.
2. 비슷하게 추상 메서드가 여러 개인 인터페이스의 인스턴스를 만들 때도 익명 클래스를 쓸 수 있다.
3. 마지막으로, 람다는 자신을 참조할 수 없다. 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다. 반면 익명 클래스에서의 this는 인스턴스 자신을 가리킨다. 그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야 한다. 이 부분이 상당히 골때리는데, 이 이유 때문에 static을 쓰면 객체지향을 너무나도 벗어나버린다.
람다도 익명 클래스처럼 직렬화 형태가 구현별로 다를 수 있다. 따라서 람다를 직렬화하는 일은 극히 삼가야 한다(익명 클래스와 인스턴스도 마찬가지다). 직렬화해야만 하는 함수 객체가 있다면(가령 위 코드의 Comparator처럼) private 정적 중첩 클래스의 인스턴스를 사용하자.
핵심 정리
자바가 8로 판올림되면서 작은 함수 객체를 구현하는 데 적합한 람다가 도입되었다. 익명 클래스 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때만 사용하라. 람다는 작은 함수 객체를 아주 쉽게 표현할 수 있어 (이전 자바에서는 실용적이지 않던) 함수형 프로그래밍의 지평을 열었다.