Effective Java 42장 - 익명 클래스보다는 람다를 사용하라

well-life-gm·2021년 11월 5일
0

이펙티브 자바

목록 보기
3/3

자바 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로 판올림되면서 작은 함수 객체를 구현하는 데 적합한 람다가 도입되었다. 익명 클래스 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 때만 사용하라. 람다는 작은 함수 객체를 아주 쉽게 표현할 수 있어 (이전 자바에서는 실용적이지 않던) 함수형 프로그래밍의 지평을 열었다.

profile
내가 보려고 만든 블로그

0개의 댓글

관련 채용 정보