모던 자바 인 액션 3장
모던 자바 인 액션 3장 : 람다 표현식
3.1 람다란 무엇인가?
람다 표현식은 앞장에서 간략하게 살펴본 것 처럼 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 볼 수 있다.
람다의 특징을 하나씩 자세히 살펴보자.
- 익명
보통의 메서드와 달리 이름이 없다. 구현해야할 코드에 대한 걱정거리가 줄어든다. - 함수
람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부른다. 하지만 메서드처럼 파라미터 리스트, 바디, 반환형식, 예외 리스트를 포함한다. - 전달
람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다. - 간결성
익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없다.
이제 람다의 구조에 대해서 책에서 설명해준다.

- 파라미터 리스트
Comparator의 compare 메서드 파라미터(Apple 두개) - 화살표
람다의 파라미터 리스트와 바디를 구분 - 람다 바디
두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식
람다표현식 예제
// String 형식 파라미터 하나를 가지며 int를 반환
// 람다표현식은 return문이 함축되어있어 명시하지 않아도된다.
(String s) -> s.length()
// Apple형식의 파라미터 하나, boolean값 리턴
(Apple a) -> a.getWeight() > 150
// 람다 바디에 여러 행의 문장 포함 가능
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x+y);
}
// 파라미터가 없으며 int 42를 반환
() -> 42
아래는 람다의 기본 문법이다. 2가지 방식으로 표현할 수 있다.
- 표현식 스타일
(parameters) -> expression - 블록 스타일
(parameters) -> { statements; }
표현식과 구문의 차이를 잘 알아두자
표현식은 return문이 함축되어 있지만 구문은 코드블록을 작성해야한다.
3.2 어디에, 어떻게 람다를 사용할까?
앞에서 우리는 Comparator 형식의 변수에 람다를 사용했었다. 또한 2장에서는 필터조건으로 람다식을 사용하였다.
정확히 어디에서 람다를 사용할 수 있다는 걸까??
3.2.1 함수형 인터페이스
2장에서 만든 Predicate
Predicate
public interface Predicate<T>{
boolean test(T t);
}
즉, 함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하는 인터페이스다.
ex) Comparator, Runnable 등
책에서 제공하는 퀴즈를 보며 개념을 이해해보자.
위 퀴즈에서 Adder만 함수형 인터페이스라는 사실을 쉽게 알 수 있을것이다.
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터페이스의 인스턴스 취급할 수 있다.
아래 예제는 Runnable의 유일한 추상메서드 run을 정의하는 코드이다.
Runnable r1 = () -> System.out.println("hello World 1");
Runnable r2 = new Runnable() {
public void run(){
System.out.println("hello World 2");
}
}
public static void process(Runnable r){
r.run();
}
process(r1);
process(r2);
process(() -> System.out.println("hello World 3"));
3.2.2 함수 디스크립터
함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시그니처를 가리킨다.
람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라고 부른다.
이 장에서는 람다와 함수형 인터페이스를 가리키는 특별한 표기법을 사용한다.
- () -> void : 파라미터 리스트가 없으며 void를 반환하는 함수
(앞서 살펴본 Runnable이 이에 해당) - (Apple, Apple) -> int : 두개의 Apple을 받아 int를 반환
결론적으로 람다표현식은 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며, 함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖는다는 사실을 기억하는 것으로 충분하다고 한다.
3.3 람다 활용 : 실행 어라운드 패턴
자원 처리에 사용하는 순환 패턴은 자원을 열고, 처리하고, 자원을 닫는 순서로 이루어진다. 설정과 정리 과정은 대부분 비슷하다.
즉, 자원 처리 코드를 설정과 정리 과정이 둘러싸는 형태를 실행 어라운드 패턴이라고 부른다
아래 예제는 한 행을 읽는 코드다.
public String processFile() throws IOException{
try(BufferedReader br =
new BufferedReader(new FileReader("data.txt"))){
return br.readLine(); // 실제 자원처리 코드
}
}
3.3.1 1단계 : 동작 파라미터화를 기억하라.
내 생각에는 2장에서 살펴본 것처럼 필터링하는 부분이 실제 로직이므로, 위 코드에서는 한줄을 읽는 실제 자원처리 코드를 함수형 인터페이스로 빼면 될 것같다.
BufferedReader를 이용해서 다른 동작을 수행할 수 있도록 processFile 메서드로 동작을 전달해야 한다.
람다를 이용해서 동작을 전달할 수 있다.
String result = processFile((BufferedReader br) -> br.readLine());
3.3.2 2단계 : 함수형 인터페이스를 이용해서 동작 전달
함수형 인터페이스 자리에 람다를 사용할 수 있다. 따라서 BufferedReader -> String 과 IOException을 던질 수 있는 시그니처와 일치하는 함수형 인터페이스를 만들자.
@FunctionalInterface // 함수형 인터페이스인지 검사하는 어노테이션
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
정의한 인터페이스를 processFile 메서드의 인수로 전달할 수 있다.
public String processFile(BufferedReaderProcessor p) throws IOException{
...
}
3.3.3 3단계 : 동작 실행
이제 BufferedReaderProcessor에 정의된 process 메서드와 일치하는 시그니처의 람다를 전달할 수 있다.
BufferedReaderProcessor의 process를 통해 전달된 코드가 실행된다.
public String processFile(BufferedReaderProcessor p) throws IOException{
try(BufferedReader br =
new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
3.3.4 4단계 : 람다 전달
이제 람다를 이용해서 다양한 동작을 processFile 메서드로 전달할 수 있다.
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
책에 위 4단계를 요약한 표가 있다. 말로 직접 설명해보면서 따라가면 이해가 잘 될것같다.

3.4 함수형 인터페이스 사용
자바 8 라이브러리 설계자들은 java.util.function 패키지로 여러 가지 새로운 함수형 인터페이스를 제공한다고 한다.
이번 절에서는 Predicate, Consumer, Function 인터페이스에 대하여 알아본다.
3.4.1 Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public <T> List<T> filter(List<T> list, Predicate<T> p){
for(T t:list){
if(p.test(t)){
result.add(t);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
3.4.2 Consumer
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public <T> void forEach(List<T> list, Consumer<T> c){
for(T t:list){
c.accept(t);
}
}
forEach(
Arrays.asList(1,2,3,4,5),
(Integer i) -> System.out.println(i) // Consumer의 accept를 구현하는 람다
)
3.4.3 Function
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
public <T, R> List<R> map(List<T> list, Function<T,R> f){
List<R> result = new ArrayList<>();
for(T t: list){
result.add(f.applt(t));
}
return result;
}
List<Integer> l = map(
Arrays.asList("lambdas", "in", "action"),
(String s) -> s.length() // Function의 apply를 구현하는 람다
)
기본형 특화
자바의 모든 형식은 참조형 or 기본형이다.
- 박싱 : 기본형에서 참조형으로 변환
ex) int형에서 Integer형를 변환 - 언박싱 : 참조형에소 기본형으로 변환
ex) Integer형에서 int 형
우리가 자주 사용하는 아래와 같은 코드도 사실은 오토박싱이라는 기능으로 자동으로 박싱과 언박싱이 이루어진다.
List<Integer> list = new ArrayList<>();
for(int i=300;i<400;i++){
list.add(i);
}
- 이런 변환은 비용이 소모된다.
- 기본형이 박싱되어 힙영역에 저장되고 따라서 박싱된 값은 메모리를 더 소비한다.
자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공한다.
아래 예제에서 IntPredicate는 1000이라는 값을 박싱하지 않지만 Predicate
public interface IntPredicate{
boolean test(int t);
}
IntPredicate evenNumbers = (int i) -> i%2==0;
evenNumbers.test(1000);
Predicate<Integer> oddNumbers = (Integer i) -> i%2!=0;
oldNumbers.test(1000);
- 일반적으로 특정 형식을 입력으로 받는 함수형 인터페이스 이름 앞에는 DoublePredicate, IntConsumer, LongBinaryOperator, IntFunction처럼 형식명이 붙는다.
- Function 인터페이스는 ToIntFunction, IntToDoubleFunction 등의 다양한 출력 형식 파라미터를 제공한다.
아래 표는 자바 8에 추가된 함수형 인터페이스의 일부를 보여준다. 필요하다면 우리가 직접 함수형 인터페이스를 정의할 수 있음을 기억하자.

예외, 람다, 함수형 인터페이스의 관계
함수형 인터페이스는 Checked Exception을 던지는 동작을 허용하지 않는다.
따라서 예외를 던지는 람다 표현식을 만들려면 함수형 인터페이스를 직접 정의하거나 람다를 try/catch 블록으로 감싸야한다.
이는 자바 라이브러리에 있는 API를 사용할때 즉, 직접 함수형 인터페이스를 만들지 않고 자바에서 제공하는 Function<T,R>형식의 인터페이스를 쓸때의 이야기다.
3.5 형식 검사, 형식 추론, 제약
글쓴이는 알고리즘 문제를 풀 때 아래와 같이 리스트를 배열 형식으로 바꾸곤 한다.
list.stream().mapToInt(x->x).toArray();
or
list.stream().mapToInt(Integer::intValue).toArray();
우리가 앞써 살펴본 람다식에서는 분명 파라미터 리스트에 타입 변수명 형식으로 작성하였는데 위 람다식에서는 타입명 혹은 변수명만 사용하고있다.
위 리스트를 배열로 바꾸는 코드는 솔직히 통째로 외우고 사용을 해왔다. 이번 절의 제목을 보니 뭔가 위와같은 코드로 작성하고 어떻게 동작하는지 알아볼 수 있을것 같다.
3.5.1 형식 검사
람다가 사용되는 컨텍스트를 이용해서 람다의 형식을 추론할 수 있다고 한다. 여기서 컨텍스트란 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수 등을 이야기한다.
- 대상 형식 : 어떤 컨텍스트에서 기대되는 람다 표현식의 형식
이후 코드를 분석하며 순서대로 형식 확인 과정을 보여준다.
List<Apple> heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
- filter 메서드의 선언을 확인한다.
- filter 메서드는 두번째 파라미터로 Predicate
형식(대상 형식)을 기대한다. - Predicate
은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스다. - test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.
- filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.
3.5.2 같은 람다, 다른 함수형 인터페이스
람다 표현식이 같더라도 함수형 인터페이스가 다를 수 있다. 즉, 하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할 수 있다.
(함수형 인터페이스의 추상메서드가 파라미터 타입, 리턴값이 같다면 당연하다.)
Comparator<Apple> c1 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
3.5.3 형식 추론
자바 컴파일러는 람다 표현식이 사용된 컨텍스트를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
컴파일러가 람다 표현식의 파라미터 형식에 접근할 수 있으므로 타입을 생략할 수 있다. 예시를 통해 살펴보자.
List<Apple> greenApples =
filter(inventory,apple -> GREEN.equals(apple.getColor()));
이때문에 3.5절 시작부분에 작성해놓은 글쓴이의 코드를 분석할 수 있다.
mapToInt 메서드의 선언은 아래와 같다.
IntStream mapToInt(ToIntFunction<? super T> mapper);
ToIntFunction은 T타입 혹은 T의 부모타입까지 받을 수 있으며, int 값을 리턴한다.
@FunctionalInterface
public interface ToIntFunction<T> {
int applyAsInt(T value);
}
즉, 리스트를 배열로 바꾸는 코드에서 Integer 값이 applayAsInt 메서드의 파라미터로 들어가며, 들어간 값이 그대로 int형으로 언박싱되는 모양이다.
3.5.4 지역 변수 사용
람다 내부에서는 자유 변수(파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수)를 활용할 수 있다. 이와 같은 동작을 람다 캡처링이라고 부른다.
int portNumber = 8080;
Runnable r = () -> System.out.println(portNumber);
자연스러운 코드이다.
그러나 자유 변수에도 제약이 있다.
- 람다는 인스턴스 변수, 정적 변수 모두 자유롭게 캡처 가능
- 지역 변수는 명시적으로 final로 선언되거나 final 처럼 취급되어야 한다.
- 즉, 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처할 수 있다.
따라서 아래는 컴파일할 수 없는 코드라고 한다.
int portNumber = 8080;
Runnable r = () -> System.out.println(portNumber);
portNumber = 1234;
위 예제에서 portNumber=1234를 추가하면 컴파일 에러가 발생한다.
지역 변수의 제약
- 인스턴스 변수는 힙 영역, 지역 변수는 스택영역에 위치
- 람다가 스레드에서 실행된다면 변수 할당이 해제되어도 람다를 실행하는 스레드에서는 해당 변수에 접근하려 할 수 있음
- 따라서 자바 구현에서는 자유 지역 변수의 복사본을 제공하여 원래 변수에 접근하지 못하도록 함
- 따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수는
final또는 사실상final로 취급되어 값을 한 번만 할당할 수 있음
3.6 메서드 참조
특정 람다 표현식을 축약한 것 : 가독성 증가
inventory.sort((Apple a1, Apple a2) ->
a1.getWeight().compareTo(a2.getWeight()));
메서드 참조와 java.util.Comparator.comparing을 활용하면 아래와 같이 코드를 바꿀 수 있다.
inventory.sort(comparing(Apple::getWeight));
이제 이 문법의 의미를 배워보자.
3.6.1 요약
메서드 참조를 이용하면 기존 메서드 구현으로 람다 표현식을 만들 수 있다.
이때 명시적으로 메서드명을 참조함으로써 가독성을 높일 수 있다
예시를 보자
Apple::getWeight() ==> (Apple apple) -> apple.getWeight()
String::substring ==> (str, i) -> str.substring(i)
실제로 메서드를 호출하는게 아니므로 괄호가 빠지고 메서드의 이름만 붙인것을 볼 수 있다.
메서드 참조를 만드는 방법
- 정적 메서드 참조
ex) Integer의 parseInt 메서드는 Integer::parseInt로 표현 - 다양한 형식의 인스턴스 메서드 참조
ex) String의 length 메서드는 String::length로 표현 - 기존 객체의 인스턴스 메서드 참조
ex) 지역변수명::메서드명
1,2 번은 이해가 쉬워도 3번째의 경우 예시를 통해서 알아보자.
3번째 유형의 메서드 참조는 비공개 헬퍼 메서드를 정의한 상황에서 유용하게 사용할 수 있다.
private boolean isValidName(String string){
return Character.isUpperCase(string.charAt(0));
}
이제 Predicate
filter(words, this::isValidName)
또한 생성자, 배열 생성자, super 호출 등에 사용할 수 있는 특별한 형식의 메서드 참조도 있다.
책에서 List에 포함된 문자열을 대소문자 구분하지 않고 정렬하는 프로그램을 구현하는데 이를 따라가보자.
일단 List의 sort 메서드는 Comparator 파라미터를 가질 수 있다. Comparator는 이전에도 본 것 처럼 (T, T) -> int라는 함수 디스크립터를 갖는다.
List<String> str = Arrays.asList("a","b","A","B");
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
위 람다식을 메서드 참조를 사용해서 다음처럼 줄일 수 있다.
List<String> str = Arrays.asList("a","b","A","B");
str.sort(String::compareToIgnoreCase);
컴파일러는 람다 표현식의 형식을 검사하던 방식과 비슷하게 메서드 참조가 주어진 함수형 인터페이스와 일치하는지 확인한다.
책에서 제공하는 퀴즈를 풀어보자.

// 1번
ToIntFunction<String> stringToInt = Integer::parseInt;
// 2번
BiPredicate<List<String>, String> contains =
List::contains;
// 3번
Predicate<String> startsWithNumber = this::startWithNumber;
지금까지는 기존의 메서드 구현을 재활용해서 메서드 참조를 만드는 방법을 살펴봤다. 이제 클래스의 생성자를 이용하는 방법을 살펴본다.
3.6.2 생성자 참조
ClassName::new처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있다.
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
위 예제는 아래 코드와 같다.
Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();
만약 인수가 없는 생성자가 아닌 Apple(Integer weight)의 시그니처를 갖는 생성자가 있다면
이는 입력값이 필요하므로 Function 인터페이스의 시그니처와 같다.
따라서 아래와 같은 코드를 구현할 수 있다.
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apple(110);
아래는 약간 활용예제 코드이다.
List<Integer> weights = Arrays.asList(7,3,4,10);
List<Apple> apples = map(weight, Apple::new);
public List<Apple> map(List<Integer>, Function<Integer, Apple> f){
List<Apple> result = new ArrayList<>();
for(Integer i:list){
result.add(f.apply(i));
}
}
만약 파라미터가 2개라면 BiFunction 인터페이스를 사용하면 된다.
이제 퀴즈를 풀며 제대로 이해했는지 확인해보자.
일단 int형 파라미터가 3개가 필요한 함수형 인터페이스를 만들어야 할 것 같다.
@FunctionalInterface
public interface TripleFunction<T,U,V,R>{
R apply(T t, U u, V v);
}
이후 Color의 생성자 참조를 수행할 수 있다.
TripleFunction<Integer, Integer, Integer, Color> triple = Color::new;
3.7 람다, 메서드 참조 활용하기
처음에 배운 사과 리스트를 정렬하는 문제를 지금까지 배운 개념들을 통해 다시 작성해본다고 한다.
아래는 이 절의 최종 목표인 코드이다.
inventory.sort(comparing(Apple::getWeight));
일단 책을 따라가기 전에 글쓴이의 생각부터 정리해보자.
List의 sort메서드는 Comparator를 파라미터로 받고 Comparator는 FunctionalInterface이다. 따라서 우리는 Comparator로 람다표현식을 전달하여 커스텀 정렬 전략을 만들 수 있다.
3.7.1 1단계 : 코드 전달
sort 메서드의 시그니처는 아래와 같다.
void sort(Comparator<? super E> c)
일단 1단계 코드는 다음과 같이 완성할 수 있다.
public class AppleComparator implements Comparator<Apple>{
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
3.7.2 2단계 : 익명 클래스 사용
1회성 코드는 1단계 처럼 구현하는 것보다는 익명클래스를 사용하는것이 좋다
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
3.7.3 람다 표현식 사용
알다시피 Comparator는 함수형 인터페이스이기 때문에 우리는 람다표현식을 사용할 수 있다.
Comparator의 함수 디스크립터는 (T,T) -> int다.
형식 추론 메커니즘까지 적용시켜 아래와 같이 코드를 작성할 수 있다.
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
Comparator는 Comparable 키를 추출해서 Comparator 객체로 만드는 Function 함수를 인수로 받는 comparing을 포함한다.
다음처럼 comparing 메서드를 사용할 수 있다.
(람다 표현식은 사과를 비교하는데 사용할 키를 어떻게 추출할 것인지 지정하는 한 개의 인수만 포함한다.)
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
Comparator를 static import하여 다음과같이 코드를 줄일 수 있다.
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight()));
3.7.4 4단계 메서드 참조
inventory.sort(comparing(Apple::getWeight));
이로써 목표 코드를 완성하였다.
3.8 람다 표현식을 조합할 수 있는 유용한 메서드
함수형 인터페이스는 다양한 유틸리티 메서드를 디폴트 메서드로 포함한다.
예를들어 여러 개의 람다 표현식을 조합해서 복잡한 람다 표현식을 만들 수도 있고 한 함수의 결과가 다른 함수의 입력이 되도록 두 함수를 조합할 수도 있다.
3.8.1 Comparator 조합
Comparator<Apple> c = Comparator.comparing(Apple::getWeight);
역정렬
역정렬을 위한 다른 Comparator 인터페이스를 만들 필요가 없다.
인터페이스 자체에서 reverse라는 디폴트 메소드를 제공하기 때문이다.
따라서 다음처럼 처음 비교자 구현을 그대로 사용하여 사과의 무게를 역정렬할 수 있다.
inventory.sort(comparing(Apple::getWeight).reverse());
Comparator 연결
만약 무게가 같은 사과가 존재하면 그 두 사과는 뭘 기준으로 정렬할까?
thenComparing 메서드로 두번쨰 비교자를 만들 수 있다.
inventory.sort(comparing(Apple::getWeight)
.reversed()
.thenComparing(Apple::getCountry));
3.8.2 Predicate 조합
Predicate는 negate, and, or 세 가지 메서드를 제공한다.
- negate : 반전
// 기존 프레디케이트 객체 redApple의 결과를 반전시킨 객체를 만든다. Predicate<Apple> notRedApple = redApple.negate(); - and : 두가지 조건
// 두 프레디케이트를 연결해서 새로운 프레이케이트 객체를 만듬 Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150); - or : 조건 하나씩
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150) .or(apple -> GREEN.equals(apple.getColor()));
3.8.3 Function 조합
Function 인터페이스는 Function 인스턴스를 반환하는 andThen, compose 두 가지 디폴트 메소드를 제공한다.
andThen은 앞을 처리 후 뒤를 처리 한다는걸 직감적으로 느낄 수 있다.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 1더하고 2곱하여 4반환
compose는 andThen과는 반대로 인수로 주어진 함수 먼저 실행 후 그 결과를 외부로 제공한다.
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1); // 2곱하고 1더하여 3반환
3.9장 비슷한 수학적 개념은 건너갑니다.
3.10 마치며
- 람다 표현식은 익명 함수의 일종이다. 이름은 없지만, 파라미터 리스트, 바디 반환형식을 가지며 예외를 던질 수 있다.
- 람다 표현식으로 간결한 코드를 구현할 수 있다.
- 함수형 인터페이스는 하나의 추상메서드만 정의하는 인터페이스다.
- 함수형 인터페이스를 기대하는 곳에서만 람다 표현식을 사용할 수 있다.
- 람다 표현식을 이용해서 함수형 인터페이스의 추상메서드를 제공할 수 있으며 람다표현식 전체가 함수형 인터페이스의 인스턴스로 취급된다.
- java.util.function 패키지는 Predicate
, Function<T, R>, Supplier , Consumer , BinaryOperator 등을 포함해서 자주 사용하는 다양한 함수형 인터페이스를 제공한다. - 자바 8은 Predicate
와 Function<T, R> 같은 제네릭 함수형 인터페이스와 관련한 박싱 동작을 피할 수 있는 IntPredicate, IntToLongFunction 등과 같은 기본형 특화 인터페이스도 제공한다. - 실행 어라운드 패턴을 람다와 활용하면 유연성과 재사용성을 추가로 얻을 수 있다.
- 람다 표현식의 기대 형식을 대상 형식이라고 한다.
- 메서드 참조를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있다.
- Comparator, Predicate, Function 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공한다.
글쓴이의 느낀점
람다식을 알지 못한상태로 람다식을 활용한 코드를 접했을때에는 사소하여 코드가 이해가 안됐다.
이번 3장에서 람다식을 적용할 수 있는 부분부터 시작하여 메서드 참조 등의 가독성을 향상시킬 수 있는 방법까지 알아보았다.
이번장의 핵심은 함수형 인터페이스라고 생각한다. 함수형 인터페이스가 사용되는 부분에 람다식을 작성할 수 있고 이로인해 동작을 파라미터화 시켜 코드조각을 전달한다는 내용이 흥미를 불러일으켰다.