람다와 메서드 참조
람다는 추상 메서드가 하나인 함수형 인터페이스의 구현을 값처럼 전달합니다.
익명 클래스보다 짧지만 타입은 주변 매개변수나 대입 대상에서 결정됩니다.
람다 본문에서 캡처하는 지역 변수는 final 또는 effectively final이어야 합니다.
메서드 참조는 기존 메서드 호출이 함수형 인터페이스의 매개변수와 맞을 때 더 직접적인 표현입니다.
이 절에서는 동작을 전달하되 숨은 가변 상태를 만들지 않는 방법을 살펴봅니다.
람다 식의 타입은 대입 대상에 의해 정해지는 목표 타입입니다. 같은 text -> text.isEmpty()도 Predicate로는 값을 반환하는 검사이고, 다른 함수형 인터페이스와 시그니처가 맞지 않으면 사용할 수 없습니다. 문법 모양만 보지 말고 인터페이스의 단일 추상 메서드를 먼저 읽어야 합니다.
함수형 인터페이스가 람다의 타입이다
Predicate, Function, Consumer, Supplier 같은 표준 인터페이스가 입력과 결과 형태를 표현합니다.
람다 문법만으로 독립 타입이 생기지는 않습니다.
캡처한 지역 변수는 바뀌지 않아야 한다
스택 프레임이 끝난 뒤에도 람다가 실행될 수 있어 값이 안정적으로 고정돼야 합니다.
가변 상태를 배열로 우회하지 않습니다.
LambdaPipeline.java는 지금 설명한 규칙을 실제 값의 변화로 보여 줍니다.
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class LambdaPipeline {
public static void main(String[] args) {
int minimum = 4;
Predicate<String> longEnough = text -> text.length() >= minimum;
Function<String, String> normalize = String::trim;
List<String> result = List.of(" Java ", "JDK", " VM ").stream()
.map(normalize)
.filter(longEnough)
.toList();
result.forEach(System.out::println);
// minimum++; // 컴파일 오류: 람다가 캡처한 지역 변수는 effectively final
}
}String::trim이 각 문자열을 정규화한 뒤, minimum 4를 캡처한 Predicate가 Java만 남깁니다. JDK와 VM은 trim 이후 길이가 기준보다 짧아 제거됩니다.
Javanormalize는 기존 메서드를 참조하고 longEnough는 고정된 minimum을 캡처해 동작을 값으로 전달합니다.
메서드 참조는 이미 있는 동작을 가리킨다
String::trim, System.out::println, User::new처럼 수신자와 인자 배치가 목표 인터페이스와 맞아야 합니다.
Supplier, Consumer, BiFunction, Function을 나란히 두고 각 메서드 참조의 인자와 반환 형태를 목표 인터페이스에 맞춰 읽어 봅니다.
java.util.function.Supplier<String> create = () -> "new";
java.util.function.Consumer<String> print = System.out::println;
java.util.function.BiFunction<String, String, String> join = String::concat;
java.util.function.Function<String, Integer> parse = Integer::valueOf;print.accept(join.apply(create.get(), " value"));// 여러 분기와 예외 처리는 이름 붙은 메서드로 추출한 뒤 메서드 참조를 사용한다.짧은 동작만 인라인에 둔다
여러 분기와 예외 처리가 들어가면 이름 붙은 메서드로 추출해 람다는 그 메서드를 참조하도록 합니다.
minimum을 3으로 바꾸면 어떤 값이 추가되는지 먼저 예상한 뒤 실행하세요. 다음으로 longEnough를 String::isBlank로 바꾸려 하면 목표 인터페이스의 의미는 맞아도 현재 filter 의도와 반대가 됩니다. 시그니처가 맞는 것과 업무 동작이 맞는 것은 별도 검토 대상입니다.
effectively final 제한은 객체 전체를 불변으로 만들지 않습니다. 캡처한 List 참조를 다시 대입할 수 없더라도 그 목록의 내용은 바뀔 수 있습니다. 비동기 실행이나 오래 보관되는 콜백에서는 이런 숨은 공유 상태가 결과를 시간에 따라 바꾸므로, 필요한 값만 불변 스냅샷으로 캡처하는 편이 안전합니다.
람다는 코드를 줄이는 문법보다 호출자가 동작을 매개변수로 조합하게 하는 타입 안전한 경계입니다.