본문 바로가기

Effective Java

7장. 람다와 스트림 - (1) 람다

자바 8에서 함수형 인터페이스, 람다, 메서드 참조라는 개념이 추가되면서 함수 객체를 더 쉽게 만들 수 있게 되었습니다.

먼저, 람다에 대해서 알아보겠습니다.

아이템 42. 익명 클래스보다는 람다를 사용하라

JDK 1.1 이후에는 함수 객체를 만드는 주요 수단은 익명 클래스였습니다.

하지만 익명클래스 방식은 코드가 길기 때문에 8버전 이전 자바는 함수형 프로그래밍에 적합하지 않았습니다.

자바8에 람다가 등장하면서 함수형 인터페이스라 부르는 인터페이스들의 인스턴스를 람다식을 사용하여 만들 수 있게 되었습니다.

## AS-IS

Collection.sort(words, new Comparator<String>() {
	public int compare(String s1, String s2) {
    	return Integer.compare(s1.length(), s2.length());
    }
});

## TO-BE

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

 

람다 코드의 매개변수 (s1, s2)의 타입은 컴파일러가 문맥을 살펴 타입을 추론해줍니다.

타입을 명시해야 코드가 더 명확할 때만 제외하고는, 람다의 모든 매개변수 타입은 생략하는게 좋습니다.

람다를 사용하면 코드가 간결해지는 이점이 있지만 항상 람다를 사용하는 방법이 좋진 않습니다.

 

람다를 사용하면 좋지 않은 경우

1. 코드 자체로 명확히 설명이 되지 않을 경우

2. 코드가 3줄 이상이 될 경우

위의 경우 람다를 사용하게되면 코드 가독성이 심하게 떨어지게 됩니다.

 

람다를 사용하면 안되는 경우

1. this 키워드를 사용하여 자기 자신을 참조해야 할 경우 (람다에서 this 키워드는 바깥 인스턴스를 가리킵니다.)

2. 직렬화를 사용해야하는 경우

익명클래스와 마찬가지로 람다는 직렬화 형태가 jvm별로 다를 수 있습니다.

직렬화해야만 하는 함수는 private 정적 중첩 클래스의 인스턴스를 사용합니다.

 

아이템 43. 람다보다는  메서드 참조를 사용하라

람다가 익명 클래스보다 나은 점 중 가장 큰 특징은 간결함입니다.

하지만 자바에서  메서드 참조(method reference)는 람다보다 더 나은 간결함을 제공합니다.

람다
map.merge(key, 1, (count, incr) -> count + incr);

메서드 참조
map.merge(key, 1, Integer::sum);

자바8에서 Map에 추가된 merge 메서드를 사용한 예입니다.

merge는 map 안에 key가 존재하지 않을 경우 [키, 값] 쌍을 저장하고 key 가 존재할 경우 [키, 함수 결과]를 저장하는 메소드입니다.

람다를 사용할 경우 세번째 인자에 매개변수인 (count, incr) 이 크게 하는 일 없이 존재해야합니다.

하지만 메서드 참조를 사용할 경우 똑같은 결과를 더 보기 좋게 얻을 수 있습니다.

람다 대신 메서드 참조를 사용하는 방법

람다로 작성할 코드를 새로운 메서드에 담은 다음, 람다 대신 그 메서드 참조를 사용합니다.

 

메서드 참조의 다섯가지 유형

1. 정적 메서드를 참조

2. 한정적 인스턴스 메서드 참조 (함수 객체가 전달 받는 인수 = 참조되는 메서드가 받는 인수)

3. 비한정적 인스턴스 메서드 참조 (함수 객체가 전달 받는 인수 != 참조되는 메서드가 받는 인수)

4. 클래스 생성자를 가리키는 메서드 참조

5. 배열 생성자를 가리키는 메서드 참조

메서드 참조 유형 같은 기능을 하는 람다
정적 Integer::parseInt str -> Integer.parseInt(str)
한정적 (인스턴스) Instant.now()::isAfter Instant then = Instant.now();
t -> then.isAfter(t)
비한정적 (인스턴스) String::toLowerCase str -> str.toLowerCase()
클래스 생성자 TreeMap<K,V>::now () -> new TreeMap<K,V>()
배열 생성자 int[]::new len -> new int[len]

정리

메서드 참조는 람다의 간단명료한 대안이 될 수 있습니다.

메서드 참조 쪽이 짧고 명확하다면 메서드 참조를 쓰고, 그렇지 않을 때만 람다를 사용합니다.

 

아이템 44. 표준 함수형 인터페이스를 사용하라

java.util.function 패키지를 보면 다양한 용도의 표준 함수형 인터페이스가 존재합니다.

표준 함수형 인터페이스들은 유용한 디폴트 메서드들도 많이 제공하므로 필요한 용도에 맞는게 있다면,

직접 구현하지 말고 표준 함수형 인터페이스를 활용하는게 좋습니다.

인터페이스 함수 시그니처
UnaryOperator<T> T apply(T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger::add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T,R> T apply(T t) Arrays::asList
Supplier<T> T get() Intant::now
Consumer<T> void accept(T t) System.out::println

직접  함수형 인터페이스를 구현하는게 더 좋을 경우

1. 자주 쓰이며, 이름 자체가 용도를 명확히 설명해준다.

2. 반드시 따라야 하는 규칙이 있다.

3. 유용한 디폴드 메서드를 제공할 수 있다.

@FunctionalInterface

직접 만든 함수형 인터페이스에는 @FunctionalInterface 애너테이션을 항상 사용해야합니다.

1. 해당 클래스의 코드나 설명 문서를 읽을 사람에게 이 인터페이스가 람다용을 설계되었음을 알려줍니다.

2. 해당 인터페이스가 추상 메서드를 두 개 이상 가지고 있을 경우 컴파일 에러가 발생합니다. (반드시 하나임을 보장해줌)

3. 유지 보수 과정에서 실수로 메서드를 추가하지 않도록 해줍니다.

 

마무리

이번엔 자바8에 추가된 람다에 대해서 알아보았습니다.

기존 익명 클래스를 사용할때보다 코드가 간결해지는 장점이 있지만 가독성이 떨어지지 않도록 주의해야합니다.

또한 람다보다 더 코드가 간결해지는 메서드 참조를 사용하는게 더 좋을듯합니다.