본문 바로가기
도서/모던 자바 인 액션

모던 자바 인 액션 - 3장 람다 표현식

by Ahngyuho 2023. 7. 28.

해당 포스팅은 모던 자바 인 액션이라는 책을 읽고 정리한 것입니다. 

 

3장 람다 표현식

2장에서 동작을 파라미터화하여 동작을 추상화해 보았습니다.

여러 요구사항에 대응할 수 있는 코드를 작성할 수 있게 되었고, 유지보수도 간편해 졌습니다.

하지만 동작 파라미터화를 위해 클래스 생성, 인스턴스화를 해주어야 하는 불편하고, 가독성이 떨어지는 단점이 있었습니다.

 

자바 8에서는 이런 문제를 람다 표현식으로 해결했습니다!

 

이번에는 동작 파라미터화를 어떤 식으로 좀 더 깔끔하게 표현할 수 있을지 배워보는 장입니다.

람다표현식과 함께 위력을 발휘하는 새로운 기능인 메서드 참조도 한번 배워봅시다!

 

3.1 람다란 무엇인가?

람다 표현식은 메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 볼 수 있습니다.

람다 표현식은 이름이 없고, 파라미터, 바디, 반환 형식, 발생 가능한 예외 리스트를 가질 수 있다는 특지이 있습니다.

람다 표현식의 특징에 대해서 알아보겠습니다.

 

  • 익명                                                                                                                                                                                  - 보통의 메서드와 달리 이름이 없으므로 익명이라 표현함. 구현해야 할 코드에 대한 걱정거리가 줄어듦.
  • 함수                                                                                                                                                                                  - 람다는 메서드처럼 특정 클래스에 종속되지 않으므로 함수라고 부름. 하지만 메서드처럼 파라미터 리스트  바디 반환 형식, 기능한 예외 리스트를 포함할 수 있음
  • 전달                                                                                                                                                                                   - 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있음
  • 간결성                                                                                                                                                                                - 익명 클래스처럼 많은 자질구레한 코드를 구현할 필요가 없음

 

람다를 사용하는 근본적인 이유는 동작 파라미터화를 위해 익명 클래스등을 사용 시 발생하는 자질구레한 코드의 간결화입니다.

 

람다를 이용해 매번 작성해야 하는 자질구레한 코드의 중복을 해결하는 것이죠.

람다 표현식을 이용하면 구현 인터페이스의 추상 메서드 바디에 직접 코드를 전달하는 것과 같은 효과를 줄 수 있습니다.

 

int compare(T o1, T o2);

이 추상 메서드를 람다 표현식을 통해 구현하는 것입니다.

 

람다 표현식 형태

  • 파라미터 리스트                                                                                                                                                                 Comparator의 compare 메서드 파라미터
  • 화살표                                                                                                                                                                               화살표 (->)는 람다의 파라미터 리스트와 바디를 구분
  • 람다 바디                                                                                                                                                                          두 사과의 무게를 비교, 람다의 반환값에 해당하는 표현식을 넣어주면 됨 (compare의 반환형은 int 이므로 이것까지 맞춰줘야 함)

3.2 어디에, 어떻게 람다를 사용할까?

함수형 인터페이스라는 문맥에서 람다식을 사용할 수 있습니다.

 

3.2.1 함수형 인터페이스

위에서 본 Comparator<T>도 함수형 인터페이스입니다. 함수형 인터페이스는 오직 하나의 추상 메서드만 지정할 수 있는 특징이 있습니다. 그렇기 때문에 동작을 파라미터화할 때 사용할 수 있었던 것입니다.(동작을 지정할 메서드가 딱 하나이므로 구현한 코드는 이 메서드의 구현일 수 밖에 없음)

 

바로 앞에서 설명드린대로 함수형 인터페이스를 쓰는 이유는 람다 표현식을 통해 추상 메서드의 구현을 직접 전달할 수 있기 때문입니다. 

 

3.2.2 함수 디스크립터

함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시그니처를 가리킵니다.

 

Comparator 인터페이스의 추상 메서드인 compare는 입력 (T,T)   반환형은 int 입니다.

이렇게 람다 표현식의 시그니처를  서술하는 메서드를 함수 디스크립터라고 부릅니다.

Comparator의 추상 메서드 compare는 인수 T 두개를 받고, 반환은 int로 하는 시그니처 입니다.

 

함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터 라고 합니다.

 

3.3 람다 활용 : 실행 어라운드 패턴

실행 어라운드 패턴

실행 어라운드 패턴은 자원을 얻어오기 위한 설정 코드 |  실제 작업 코드  |   정리 코드  

이런 형태를 띄는 것을 실행 어라운드 패턴이라고 합니다.

설정 코드와 정리 코드는 중복되고 실제 작업 코드는 작업 마다 달라지는 것이 특징인데,

이렇게 되면 동작 하나마다 하나의 메서드를 정의해줘야 하고, 거기서 설정 코드와 정리 코드는 항상 중복됩니다...

 

하지만 2장에서 동작의 파라미터를 배웠기 때문에 이 실제 작업 코드를 파라미터화 하는 것이 가능해졌습니다!

 

3.3.1 동작 파라미터화 기억하기

이제 이 br.readLine 이라는 부분을 파라미터화 해서 원하는 동작을 하도록 만들어 줄겁니다.

 

3.3.2 함수형 인터페이스 만들기

함수형 인터페이스를 만들 때 시그니처와 일치하는 디스크립터를 만들어야 합니다.

이 함수의 동작을 파라미터화 할 때 람다를 사용하기 위해서는 함수형 인터페이스를 만들어야 한는데 동작에 기반해서 디스크립터를 뽑아내어 인터페이스를 만들어줘야 합니다.

동작은 BufferedReader를 받아서 String형으로 반환해야 합니다.

(BufferedReader br) -> String

 

3.3.3 3단계: 동작 실행

이제 BufferedReader를 입력으로 받고 String을 반환하는 추상 메서드를 이런 식으로 넣어주면 됩니다.

 

3.3.4 4단계: 람다 전달

이제 람다를 이용해서 구현 코드를 함수형 인터페이스의 추상 메서드에 직접 전달하는 것이 가능해 졌습니다. 

 

실행 어라운트 패턴을 사용해 동작을 추상화하는 단계

1. 메서드의 일정 동작 패턴 중복 확인

2. 동작 파라미터화를 위해 함수 디스크립터 추출

3. 함수 디스크립터를 통해 함수형 인터페이스 작성

4. 람다 적용으로 동작 파라미터의 편의성 및 가독성 업그레이드

 

3.4 함수형 인터페이스 사용

함수형 인터페이스는 오직 하나의 추상 메서드를 가지는 인터페이스임을 앞에서 확인했습니다.

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

 

그리고 자바에서는 다양한 람다 표현식을 위해 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합을 제공합니다.

 

Comparable, Runnable, Callable

java.util.function 패키지에는 이런 여러가지의 함수형 인터페이스를 제공합니다.

 

그 중에서 Predicate, Consumer, Funciont 인터페이스에 대해서 알아봅시다.

 

3.4.1 Predicate

java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하며 test는 제네릭 형식 T의 객체를 인수로 받아 boolean 타입으로 반환합니다.

 

 

Predicate 사용예제

람다 표현식 사용예제

3.4.2 Consumer

Java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의합니다.

 

 

T 형식의 객체를 인수로 받아서 어떤 동작을 실행하고 싶은 경우 사용할 수 있습니다.

 

Consumer 사용예제

람다 표현식 사용예제

3.4.3 Function

java.util.function.Function<T,R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R객체를 반환하는 추상 메서드 apply를 정의합니다.

 

Function 사용예제

T를 받아와서 R로 변경하는 함수

Apply에 해당하는 동작은 람다 표현식을 통해 넣어주면 됩니다.

이런 식으로 String 형태의 객체를 받아와서 Integer 형태로 변경하는 메서드 참조!

 

 

3.6 메서드 참조

메서드 참조를 이용해서 기존의 메서드 정의를 재활용해서 람다처럼 전달할 수 있습니다.

 

3.6.1 요약

메서드 참조의 중요성

람다가 이 메서드를 직접 호출해라고 명령한다면 메서드를 어떻게 호출해야 하는지 설명을 참조하기보다는 메서드명을 직접 참조하는 것이 더 편리해보입니다.

메서드 명을 직접 명시하므로 가독성도 높아집니다.

 

메서드 참조 활용법

메서드명 앞에 :: 를 붙이는 방식으로 메서드 참조를 활용할 수 있습니다.

Integer::parseIntInteger 클래스에 정의된 parseInt의 메서드 참조입니다.

실제로 메서드를 호출하는 것이 아니므로 괄호가 필요 없습니다.

Integer::parseInt(String S) -> Integer.parseInt(S)의 축약표현이라고 볼 수 있습니다.

 

정확히 메서드 참조란 하나의 메서드를 참조하는 람다를 편리하게 표현할 수 있는 문법입니다

 

 

메서드 참조 만드는 법

세 가지 유형이 존재합니다.

1. 정적 메서드 참조

Integer::parseInt

2. 다양한 형식의 인스턴스 메서드 참조

String::length

3. 기존 객체의 인스턴스 메서드 참조

Transaction 객체를 할당받은 expensiveTransaction 지역 변수가 있고, 이 객체가 getVaule라는 메서드가 있다면 expensiveTransaction::getValue라고 표현할 수 있습니다.

하나의 메서드를 참조하고 있는 람다 표현식을 좀 더 편리하게 표현할 수 있게 해주는 것이 메서드 참조입니다.

Comparator(T,T) -> int 라는 함수 디스크립터를 가집니다.

 

이제 클래스의 생성자를 이용해서 메서드 참조하는 방법을 알아보겠습니다.

 

3.6.2 생성자 참조

ClassName::new 처럼 클래스명과 new 키워드를 이용해서 기존 생성자의 참조를 만들 수 있습니다.

Integer 형식을 받아 Apple 을 반환하는 시그니처를 가진다면 Funtion 인터페이스를 사용해서 다음과 같이 메서드 참조를 만들 수 있습니다.

 

두개의 인수를 가지는 경우는 BiFunction 인터페이스를 사용할 수 있습니다.

(Integer,Color) -> Apple 라는 시그니처를 가집니다.

 

하지만 이렇게 인스턴스화 하지 않고도 생성자에 접근할 수 있는 방법이 있습니다.

StringInteger가 주어지면 다양한 무게를 가지는 여러 종류의 과일을 만드는 giveMeFruit 메서드를 만들 수 있습니다.