0%

람다 표현식

람다 표현식 은 메서드로 전달할 수 있는 익명 함수를 단순화한 것으로 람다의 특징은 다음과 같다.

  • 익명 : 보통 메서드와 달리 이름이 없다.
  • 함수 : 특정클래스의 종속되지 않는다.
  • 전달 : 인수로 전달하거나 변수로 저장할 수 있다.
  • 간결성 : 익명 클래스처럼 자질구레한 코드 구현이 필요없다.

람다는 세부분으로 이루어 진다.

1
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

  • 람다 파라미터 : (Apple a1, Apple a2)
  • 화살표 : ->
  • 람다 바디 : a1.getWeight().compareTo(a2.getWeight())

람다의 기본 문법

다음은 람다의 기본 문법이다.

1
2
3
(parameters) -> expression

(parameters) -> {statements;}

람다 사용법

람다 표현식은 함수형 인터페이스 라는 문맥에서 사용할 수 있다.

함수형 인터페이스

함수형 인터페이스 는 정확히 하나의 추상 메서드를 지정하는 인터페이스다.
우리가 잘 아는 자바 API의 함수형 인터페이스는 Comparator, Runnable 등이 있다.

1
2
3
4
5
6
7
public interface Comparator<T> {
int compare(T o1, T o2);
}

public interface Runnable {
void run();
}

함수 디스크립터

함수 디스크립터 는 람다표현식의 시그니처를 의미한다. 다른말로 함수형 인터페이스의 추상 메서드 시그니처와 같다.
예) Predicate 의 함수 디스크립터는 T -> boolean 이다.

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

실행 어라운드 패턴은 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태를 갖는다.
파일 읽는 예제를 통해, 변환과정을 알아보자.

1
2
3
4
5
public static String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return br.readLine(); // 실제 필요한 작업을 하는 행
}
}

1단계 : 동작 파라미터화

실제 필요한 작업 내용이 달라질 수 있기 때문에 processFile의 동작을 파라미터화 할 필요가 있다.

1
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());

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

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}

public static String processFile(BufferedReaderProcessor p) throws IOException {
...
}

3단계 : 동작 실행

1
2
3
4
5
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}

4단계 : 람다 전달

이제 람다를 이용해 다양한 동작을 processFile 메서드로 전달할 수 있다.

1
2
3
String oneLine = processFile((BufferedReader br) -> br.readLine());

String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());

함수형 인터페이스 사용

자바 8 라이브러리 설계자들은 java.util.function 패키지로 여러가지 새로운 함수형 인터페이스를 제공한다.

Predicate

Predicate 인터페이스는 test 추상 메서드를 정의하고, test는 제네릭 형식 T의 객체를 인수로 받아 불린을 반환한다.

1
2
3
4
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

##
Consumer
Consumer 인터페이스는 accept 추상 메서드를 정의하고, accept는 제네릭 형식 T 객체를 받아서 void를 반환한다.

1
2
3
4
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

Function

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

1
2
3
4
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

기본형 특화

제네릭 파라미터에는 참조형(reference type)만 사용가능하여 박싱/언박싱 작업이 필요하고, 이러한 변환 과정은 비용소모가 된다. 불필요한 비용소모를 방지하기 위해 기본형특화 버전의 함수형 이넡페이스를 제공한다.
예) IntPredicate, LongPredicate, DoublePredicate..

대표적 함수형 인터페이스

함수형 인터페이스 함수 디스크립터
Predicate T -> boolean
Consumer T -> void
Function<T, R> T -> R
Supplier () -> T
BinaryOperator (T, T) -> T
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void
BiFunctional<T, U, R> (T, U) -> R

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

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

대상형식 이란, 어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 일컫는다.
대상 형식이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.

형식 추론

자바 컴파일러는 람다 표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다. 컴파일러는 람다의 시그니처도 추론할 수 있고, 람다표현식의 파라미터 형식에 접근할 수 있다.

자바 컴파일러는 다음처럼 람다 파라미터 형식을 추론할 수 있다.

1
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor())); // 파라미터 a에는 타입을 명시적으로 지정하지 않았다.

지역변수 사용

람다에서는 외부변수를 사용할 수 있으며 이와 같은동작을 람다 캡처링 이라고 부른다.

1
2
int portNumber = 3389;
Runnable r = () -> System.out.println(portNumber);

제약이 있다면, portNumber를 final로 선언하거나 final이 선언된 변수와 같이 사용(초기화 후 변경x)되어야 한다.

메서드 레퍼런스

메서드 레퍼런스 는 특정 메서드만을 호출하는 람다의 축양형이라 생각할 수 있다.
메서드 레퍼런스는 세가지 유형으로 구분 할 수 있으며, 아래와 같다.

  1. 정적 메서드 레퍼런스
  2. 다양한 형식의 인스턴스 메서드 레퍼런스
  3. 기존 객체의 인스턴스 메서드 레퍼런스

생성자 레퍼런스

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

1
2
3
4
5
6
7
// 생성자 레퍼런스 방식
Supplier<Apple> farm = Apple::new;
Apple apple = farm.get();

// 람다 방식
Supplier<Apple> farm = () -> new Apple();
Apple apple = farm.get();

Reference : Java 8 in Action