본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로프로그래밍 › Java › 기초

[Java] Lambda Expressions(람다식)

목차

Java Lambda Expressions(람다식)

람다식은 함수형 인터페이스의 한 추상 메서드를 구현하는 코드를 간결하게 표현한다. Runnable의 run(), Function<T,R>의 apply() 같은 계약이 대상이다. 익명 클래스 문법보다 짧지만, 람다식 자체가 아무 타입도 없이 독립된 함수 값으로 존재하는 것은 아니다. 대입되는 변수나 메서드 인자에서 대상 타입(target type)을 알아야 컴파일러가 매개변수와 반환 타입을 판단한다.

아래 조각은 jshell에서 순서대로 입력하거나, 변수와 실행문을 main 메서드 안에 두어 확인할 수 있다.

import java.util.function.BiFunction;

BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(10, 20)); // 30

Runnable log = () -> System.out.println("시작");
log.run(); // 시작

BiFunction은 추상 메서드 apply를 하나 갖는다. a, b는 대상 타입에서 Integer로 추론되고 결과도 Integer여야 한다. 두 번째 람다는 인자가 없으므로 ()를 쓴다. 원문의 function2 (value1, value2) -> ...에는 대입 연산자 =가 빠져 컴파일되지 않았다.

표현식 본문과 블록 본문

java.util.function.Function<Integer, Integer> square = x -> x * x;
java.util.function.Function<Integer, Integer> checked = x -> {
    if (x < 0) throw new IllegalArgumentException("음수");
    return x * x;
};

표현식 본문 x -> x * x는 결과를 암묵적으로 반환한다. { ... } 블록으로 감싸면 반환값이 필요한 함수형 인터페이스에서는 명시적으로 return을 쓴다. 블록 끝 세미콜론과 람다식을 변수에 대입한 문장의 끝 세미콜론을 구분한다. void 메서드라면 값을 반환하지 않아도 된다.

함수형 인터페이스에는 추상 메서드가 하나 있어야 한다. default·static 메서드는 추가할 수 있다. @FunctionalInterface를 붙이면 추상 메서드를 실수로 늘렸을 때 컴파일러가 오류를 내므로 공개 계약에 유용하다.

@FunctionalInterface
interface Formatter {
    String format(String input);
    default String fallback() { return "-"; }
}
Formatter upper = text -> text.toUpperCase();
System.out.println(upper.format("hello")); // HELLO

익명 클래스와 this의 차이

익명 클래스 안의 this는 익명 클래스 인스턴스를 가리킨다. 람다식 안의 this는 람다가 선언된 바깥 클래스의 this와 같다. 따라서 콜백에서 외부 객체의 필드를 읽는 코드를 바꿀 때 의미가 달라질 수 있다.

class Counter {
    private int count = 3;
    Runnable task() {
        return () -> System.out.println(this.count); // Counter의 this
    }
}
new Counter().task().run(); // 3

익명 클래스가 필요한 경우도 있다. 자신만의 상태나 this가 필요할 수 있고, 메서드가 여러 개인 인터페이스는 람다식으로 구현할 수 없다. 람다가 바깥 지역 변수를 캡처할 때는 그 변수가 final 또는 사실상 final(effectively final)이어야 한다.

int factor = 2;
java.util.function.IntUnaryOperator multiply = value -> value * factor;
// factor = 3; // 캡처한 지역 변수의 재할당으로 컴파일 오류

값이 계속 바뀌는 상태를 람다에서 공유하고 싶다면 지역 변수 하나를 억지로 캡처하기보다 상태의 소유자와 동시성 규칙을 먼저 정한다. 스트림의 map·filter 같은 곳에는 부작용이 적은 람다가 결과를 예측하기 쉽다. 람다 본문에서 예외를 던지면 그 함수를 호출한 API의 오류 처리 방식도 함께 확인해야 한다.

참고: Java 언어 명세: Lambda Expressions, Oracle Function API

같은 카테고리의 글