int total = 0;
orders.forEach(o -> total += o.amount()); // 컴파일 에러: total 은 effectively final 이어야 함람다는 스택의 지역 변수를 값으로 복사해 가지므로 원본을 바꿀 수 없습니다(2.6). int[] 편법은 병렬 스트림에서 경쟁 조건이 됩니다.
✅
long total = orders.stream().mapToLong(Order::amount).sum();
// 또는 AtomicLong / 루프 사용var 나 Object 에 람다 대입var f = x -> x * 2; // 컴파일 에러: 목표 타입 없음
Object o = () -> {}; // 컴파일 에러: Object 는 함수형 인터페이스가 아님람다는 스스로 타입이 없고 대입 대상의 함수형 인터페이스 타입으로만 해석됩니다.
✅
IntUnaryOperator f = x -> x * 2;
Runnable o = () -> {};
Object o2 = (Runnable) () -> {}; // 캐스팅으로 목표 타입 지정List<Path> paths = ...;
paths.forEach(p -> Files.readString(p)); // 컴파일 에러: IOException 을 Consumer.accept 가 던질 수 없음Consumer.accept 는 throws 절이 없습니다. 람다 본문의 체크 예외는 인터페이스 시그니처에 맞게 처리해야 합니다.
✅
paths.forEach(p -> {
try { System.out.println(Files.readString(p)); }
catch (IOException e) { throw new UncheckedIOException(e); }
});
// 반복된다면 체크 예외를 감싸는 유틸 함수형 인터페이스(ThrowingFunction)를 하나 정의orders.stream().filter(o -> {
if (o.status().equals("PAID")) { if (o.amount() > 1000) { if (!o.customer().isBlank()) return true; } }
return false;
}).toList();람다는 "짧은 동작"을 위한 도구입니다. 3줄을 넘으면 이름 있는 메서드로 빼고 메서드 참조로 넘기는 것이 디버깅과 테스트에 유리합니다.
✅
static boolean isBillable(Order o) {
return o.status().equals("PAID") && o.amount() > 1000 && !o.customer().isBlank();
}
orders.stream().filter(Pipelines::isBillable).toList();this 습관을 람다에 적용class Handler {
String id = "H1";
Runnable make() {
return () -> {
String id = "local";
System.out.println(this.id); // "H1" — this 는 Handler. 익명 클래스였다면 자기 자신
};
}
}혼동 자체가 버그의 씨앗입니다. 람다에서 this 는 항상 감싸는 인스턴스라는 것을 기억하고, 상태가 필요한 핸들러는 명시적 클래스로 만드세요.
✅
// 람다에서 바깥 인스턴스 필드에 접근하는 것이 의도라면 this 없이 필드명만 쓰거나, 명확히 Handler.this.idFunction<Integer, Integer> square = x -> x * x;
IntStream.range(0, 1_000_000).boxed().map(square)... // 백만 번 박싱/언박싱✅
IntUnaryOperator square = x -> x * x;
IntStream.range(0, 1_000_000).map(square).sum();