#03장 람다 표현식

Raoul-Gabriel Urma 저, 우정은 역 「Modern Java in Action, 2019」를 읽고 정리하였습니다.

1. 이장의 주요 내용

  • 어디에 어떻게 람다를 사용하는가?

  • Execute-Around 패턴

  • 함수형 인터페이스, 형식 추론

  • 메서드 참조

  • 람다 만들어 보기

람다표현식이란 메서드로 전달할 수 없는 익명함수를 단순화한 것이다. 람다 표현식에는 이름이 없지만 파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외리스트들을 가질 수 있다.

람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다. 하지만 메서드 처럼 파라미터 리스트, 바디, 반환형식, 가능한 예외리스트를 포함한다.

람다 코드 미리보자.

// 람다 사용 x
Comparator<Apple> byWeight = new Comparator<Apple>() {
 public int compare(Apple a1, Apple a2){
  return a1.getWeight().compareTo(a2.getWeight());
 }
};

// 람다 사용 o
 Comparator<Apple> byWeight =
   (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
   //Lambda parameters + Lambda body

기본 문법과 유효한 람다 표현식

문제 - 올바르지 않는 문법을 찾아보자.

정답은 4번과 5번이다. return은 제어흐름을 나타때 새용하기 때문에, 4번에 return을 대가(curly braces)로 감싸야 된다. 5번의 아이언맨은 statement가 아니라 expression이기 때문에 대가로와 세미콜론을 없애고 사용해야한다. i.e. (String s) -> "아이언맨"

2. 어디에 어떻게 람다가 사용될까?

함수형 인터페이스 문맥에서만 람다 표현식을 사용할 수 있다. 함수형 인터페이스는 정확히 하나의 추상 메서드를 가진 인터페이스다. 전 챕에서 보았던, Predicate<T>에는 test 한개 추상 메서드만 있으므로 함수형 인터페이스이다.

함수형 인터페이스 종류들

이외에도 더 있고 필요하면 직접 함수형 인터페이스를 만들 수 있다.

함수형 인터페이스

함수 디스크립터

기본형 특화

Predicate<T>

T -> boolean

IntPredicate, LongPredicate, DoublePredicate

Consumer<T>

T -> void

IntConsumer, LongConsumer, DoubleConsumer

Function<T, R>

T -> R

IntFunction, IntToDoubleFunction, IntToLongFunction, LongFunction, LongToDoubleFunction, LongToIntFunction, DoubleFunction, DoubleToIntFunction, DoubleToLongFunction, ToIntFunction, ToDoubleFunction, ToLongFunction

Supplier<T>

() -> T

BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier

UnaryOperator<T>

T -> T

IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator

BinaryOperator<T>

(T, T) -> T

IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator

BiPredicate<T, U>

(T, U) -> boolean

BiConsumer<T, U>

(T, U) -> void

ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer

BiFunction<T, U, R>

(T, U) -> R

ToIntBiFunction, ToLongBiFunction, ToDoubleBiFunction

그런데 어떤 경우에 메서드가 여러개 있을때도 함수형 인터페이스라고 하는데, 추상메서드 이외의 메서드들이 디폴트 메서드일 경우 함수형 인터페이스 용어를 취할 수 있다.

문제 - 다음중 함수형 인터페이스가 아닌 것은?

첫번째만이 함수형 인터페이스다. 두번째는 Addr를 상속받아 메서드가 2개가 됨으로 제외되고, 세번째는 메서드가 없어서 제외된다.

함수 디스크립터(Function descriptor)

람다표현식은 함수형 인터페이스의 추상메서드와 같은 시그니처를 갖아야 된다. 시그니처는 인수와 반환값에 따라 달라지는 성질이다. 예를 들어, 아래 Runnable 인터페이에서 run 메서드를 보면 인수와 반환값이 모두 없는 시그니처를 가졌다고 할 수 있다.

왜 함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있을까? 20,21 장에서 자세히.

문제 - 올바른 코드를 찾아보자.

첫번째, 시그니처는 ( ) -> void이며 Runnable의 추상 메서드 run의 시그니처와 일치함으로 유효한 표현식이다. 두번째, Callable<String> 시그니처는 ( ) -> String이 됨으로 유효한 표현식이다. 세번째, 사용한 람다표현식의 시그니처가 (Apple) -> Integer이므로 Predicate<Apple>: (Apple) -> boolean과 맞지 않아 유효하지가 않다.

3. Execute Around 패턴 - 람다 활용

setup과 cleanup이 로직 앞뒤로 있는 패턴이다.

3.1 1단계: 동작 파라미터화 기억하기

3.2 2단계: 함수형 인터페이스를 이용해서 동작 전달

시그니처(BufferedReader -> String)와 일치하는 함수형 인터페이스를 만든다.

3.3 3단계: 동작 실행

3.4 4단계: 람다 전달

4. 함수형 인터페이스 사용

함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사한다. 추상메서드 시그니처를 함수 디스크립터라고 한다.

4.1 Predicate

java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하고 있고, 제너릭 형식 T의 객체를 인수로 받아 boolean을 반환한다. T는 받고 싶은 객체이고 Predicate 만족하는 시그니처는 T -> boolean.

4.2 Consumer

제너릭 형식 T 객체를 받아서 void를 반환하는 accept 추상화 메서드를 가진다.

4.3 Function

제너릭 형식 T를 인수로 받아서 제너릭형식 R 객체를 반환하는 추상메서드 apply를 가진다.

참고 - 기본형 특화

아래 코드는 자바의 오토박싱이라는 기능을 보여준다. 기본형(int)를 참조형(Integer)으로 박싱.

오토박싱을 피할 수 있도록 IntPredicate 같은 특별한 버전의 함수형 인터페이스를 제공한다.

문제 - 어떤 함수형 인터페이스를 사용할 수 있는지 생각해보자.

5. 형식 검사, 형식 추론, 제약

5.1 형식 검사

람다표현식이 유효한지 형식 검사를 다음과 같이 진행한다.

Raoul-Gabriel Urma, 「Modern Java in Action」, MANNING, 2019

5.2 같은 람다, 다른 함수형 인터페이

5.3 형식 추론

5.4 지역변수 사용

6. 메서드 참조

Last updated