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

메서드 활용 패턴 (고급)

섹션 6진행 0 / 10
1왜 배우는가2핵심 원리3패턴 모음4자주 하는 실수 (Tip)5연습 문제6정리‹ 이전다음 ›

4. 자주 하는 실수 (Tip)

❌ 실수 1: 람다가 캡처한 변수를 바꾸려 함

java
int count = 0;
list.forEach(x -> count++);      // 컴파일 오류: local variables referenced from a lambda expression must be final or effectively final

람다는 변수의 값을 복사해 갖습니다. 바깥 변수를 바꿀 수 있게 하면 복사본과 원본이 어긋나므로 언어가 막습니다.

✅ 카운트는 스트림 연산으로, 정말 상태가 필요하면 AtomicInteger나 배열 한 칸을 씁니다.

java
long count = list.stream().filter(...).count();
// 또는
AtomicInteger count = new AtomicInteger();
list.forEach(x -> count.incrementAndGet());

❌ 실수 2: thenApply에 CompletableFuture를 돌려주는 함수를 넣음

java
CompletableFuture<CompletableFuture<String>> nested = findUserId("kim").thenApply(Main::fetchEmail);
nested.join().join();     // 두 번 join해야 값이 나옴

thenApply는 반환값을 그대로 감쌉니다. 비동기 함수를 이으면 Future<Future<T>>가 됩니다.

✅ thenCompose를 씁니다.

java
CompletableFuture<String> flat = findUserId("kim").thenCompose(Main::fetchEmail);

❌ 실수 3: 재귀 안에서 computeIfAbsent로 메모이제이션

java
static Map<Integer, Long> memo = new HashMap<>();
static long fib(int n) {
    if (n < 2) return n;
    return memo.computeIfAbsent(n, k -> fib(k - 1) + fib(k - 2));   // ConcurrentModificationException
}

computeIfAbsent가 실행되는 동안 같은 맵을 재귀가 수정합니다. HashMap은 이를 감지해 예외를 던집니다.

✅ get으로 확인하고 계산 후 put합니다(패턴 14의 fib).

❌ 실수 4: 값을 넘겨서 지연 평가가 안 됨

java
String v = valueOrDefault(cached, loadFromDb());     // cached가 있어도 DB를 읽음
Optional<String> o = ...;
o.orElse(loadFromDb());                              // 값이 있어도 DB를 읽음

인자는 메서드 호출 전에 평가됩니다. 비싼 기본값을 인자로 직접 넘기면 항상 실행됩니다.

✅ Supplier로 넘깁니다: valueOrDefault(cached, () -> loadFromDb()), o.orElseGet(() -> loadFromDb()).

❌ 실수 5: 메서드 참조에 바로 andThen을 붙임

java
Function<String, Integer> f = String::trim.andThen(String::length);   // 컴파일 오류

메서드 참조는 그 자체로 타입이 없습니다(target type이 정해져야 함). .andThen을 부를 수신 객체가 없습니다.

✅ 변수에 담거나 캐스팅합니다.

java
Function<String, String> trim = String::trim;
Function<String, Integer> f = trim.andThen(String::length);
// 또는 ((Function<String, String>) String::trim).andThen(String::length)

❌ 실수 6: 독립 작업을 thenCompose로 직렬 연결

java
CompletableFuture<Integer> total = fetchPrice()
        .thenCompose(p -> fetchShipping().thenApply(s -> p + s));   // 배송비 조회가 가격 조회 뒤에 시작

배송비는 가격에 의존하지 않는데 thenCompose 안에서 만들면 가격이 끝나야 시작합니다. 두 작업 시간이 합산됩니다.

✅ 먼저 둘 다 시작하고 thenCombine으로 합칩니다.

java
CompletableFuture<Integer> price = fetchPrice();
CompletableFuture<Integer> shipping = fetchShipping();
CompletableFuture<Integer> total = price.thenCombine(shipping, Integer::sum);

❌ 실수 7: 제네릭 메서드에서 타입 매개변수 선언을 빠뜨림

java
static T firstOr(List<T> list, T fallback) { ... }     // 컴파일 오류: cannot find symbol T

클래스가 제네릭이 아니면 메서드가 <T>를 직접 선언해야 합니다. 반환 타입 앞에 씁니다.

✅ static <T> T firstOr(List<T> list, T fallback).

자주 하는 실수 (Tip)
  • ❌ 실수 1: 람다가 캡처한 변수를 바꾸려 함
  • ❌ 실수 2: thenApply에 CompletableFuture를 돌려주는 함수를 넣음
  • ❌ 실수 3: 재귀 안에서 computeIfAbsent로 메모이제이션
  • ❌ 실수 4: 값을 넘겨서 지연 평가가 안 됨
  • ❌ 실수 5: 메서드 참조에 바로 andThen을 붙임
  • ❌ 실수 6: 독립 작업을 thenCompose로 직렬 연결
  • ❌ 실수 7: 제네릭 메서드에서 타입 매개변수 선언을 빠뜨림
이전 섹션3 패턴 모음4 / 6다음 섹션5 연습 문제