공공부하자개발 · 영어 학습 노트
자바
고급모던 자바와 성능0/10 완료
  • 01제네릭과 와일드카드
  • 02멀티스레드와 동기화
  • 03람다식과 함수형 인터페이스
  • 04Stream API와 병렬 처리
  • 05Optional로 NPE 방지
  • 06메서드 활용 패턴 (고급)
  • 07Java 21 모던 문법
  • 08어노테이션·리플렉션·동적 프록시
  • 09CompletableFuture 심화와 가상 스레드 실전
  • 10JVM 메모리·GC·OOM 진단
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 고급 › 03 / 10

람다식과 함수형 인터페이스

섹션 7진행 0 / 10
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

5. 자주 하는 실수 (Tip)

❌ 실수 1: 람다 안에서 지역 변수 재대입 시도

java
int total = 0;
orders.forEach(o -> total += o.amount());   // 컴파일 에러: total 은 effectively final 이어야 함

람다는 스택의 지역 변수를 값으로 복사해 가지므로 원본을 바꿀 수 없습니다(2.6). int[] 편법은 병렬 스트림에서 경쟁 조건이 됩니다.

java
✅
long total = orders.stream().mapToLong(Order::amount).sum();
// 또는 AtomicLong / 루프 사용

❌ 실수 2: var 나 Object 에 람다 대입

java
var f = x -> x * 2;             // 컴파일 에러: 목표 타입 없음
Object o = () -> {};            // 컴파일 에러: Object 는 함수형 인터페이스가 아님

람다는 스스로 타입이 없고 대입 대상의 함수형 인터페이스 타입으로만 해석됩니다.

java
✅
IntUnaryOperator f = x -> x * 2;
Runnable o = () -> {};
Object o2 = (Runnable) () -> {};   // 캐스팅으로 목표 타입 지정

❌ 실수 3: 람다 안에서 checked exception 던지기

java
List<Path> paths = ...;
paths.forEach(p -> Files.readString(p));   // 컴파일 에러: IOException 을 Consumer.accept 가 던질 수 없음

Consumer.accept 는 throws 절이 없습니다. 람다 본문의 체크 예외는 인터페이스 시그니처에 맞게 처리해야 합니다.

java
✅
paths.forEach(p -> {
    try { System.out.println(Files.readString(p)); }
    catch (IOException e) { throw new UncheckedIOException(e); }
});
// 반복된다면 체크 예외를 감싸는 유틸 함수형 인터페이스(ThrowingFunction)를 하나 정의

❌ 실수 4: 람다에 긴 로직을 넣어 가독성 파괴

java
orders.stream().filter(o -> {
    if (o.status().equals("PAID")) { if (o.amount() > 1000) { if (!o.customer().isBlank()) return true; } }
    return false;
}).toList();

람다는 "짧은 동작"을 위한 도구입니다. 3줄을 넘으면 이름 있는 메서드로 빼고 메서드 참조로 넘기는 것이 디버깅과 테스트에 유리합니다.

java
✅
static boolean isBillable(Order o) {
    return o.status().equals("PAID") && o.amount() > 1000 && !o.customer().isBlank();
}
orders.stream().filter(Pipelines::isBillable).toList();

❌ 실수 5: 익명 클래스 this 습관을 람다에 적용

java
class Handler {
    String id = "H1";
    Runnable make() {
        return () -> {
            String id = "local";
            System.out.println(this.id);   // "H1" — this 는 Handler. 익명 클래스였다면 자기 자신
        };
    }
}

혼동 자체가 버그의 씨앗입니다. 람다에서 this 는 항상 감싸는 인스턴스라는 것을 기억하고, 상태가 필요한 핸들러는 명시적 클래스로 만드세요.

java
✅
// 람다에서 바깥 인스턴스 필드에 접근하는 것이 의도라면 this 없이 필드명만 쓰거나, 명확히 Handler.this.id

❌ 실수 6: 기본형 스트림에서 박싱 인터페이스 사용

java
Function<Integer, Integer> square = x -> x * x;
IntStream.range(0, 1_000_000).boxed().map(square)...   // 백만 번 박싱/언박싱
java
✅
IntUnaryOperator square = x -> x * x;
IntStream.range(0, 1_000_000).map(square).sum();
자주 하는 실수 (Tip)
  • ❌ 실수 1: 람다 안에서 지역 변수 재대입 시도
  • ❌ 실수 2: var 나 Object 에 람다 대입
  • ❌ 실수 3: 람다 안에서 checked exception 던지기
  • ❌ 실수 4: 람다에 긴 로직을 넣어 가독성 파괴
  • ❌ 실수 5: 익명 클래스 this 습관을 람다에 적용
  • ❌ 실수 6: 기본형 스트림에서 박싱 인터페이스 사용
이전 섹션4 응용 변형 예제5 / 7다음 섹션6 연습 문제