공공부하자개발 · 영어 학습 노트
자바
고급모던 자바와 성능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정리‹ 이전다음 ›

5. 연습 문제

문제 1

함수를 받아 "실행 시간을 측정하고 결과를 돌려주는" 데코레이터 static <T> Supplier<T> timed(String label, Supplier<T> action)을 작성하세요. 실행 후 "[label] N ms"를 출력하고(N은 System.nanoTime 차이를 ms로 변환, 정확한 수치는 무관) 원래 결과를 그대로 돌려줍니다.

그 다음 Fn.retry와 timed를 겹쳐서 적용해 보세요(재시도 전체 시간이 측정되어야 함).

정답 보기
java
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Q1 {
    static <T> Supplier<T> timed(String label, Supplier<T> action) {
        return () -> {
            long start = System.nanoTime();
            try {
                return action.get();
            } finally {                                     // 예외가 나도 시간은 찍힘
                long ms = (System.nanoTime() - start) / 1_000_000;
                System.out.println("[" + label + "] " + ms + " ms");
            }
        };
    }

    public static void main(String[] args) {
        Supplier<Integer> work = () -> { int s = 0; for (int i = 0; i < 1_000_000; i++) s += i % 7; return s; };
        System.out.println(timed("sum", work).get());
        // 출력: [sum] 3 ms   (숫자는 환경마다 다름)
        // 출력: 2999997

        AtomicInteger n = new AtomicInteger();
        Supplier<String> flaky = () -> {
            if (n.incrementAndGet() < 2) throw new RuntimeException("fail");
            return "ok";
        };
        // 바깥 timed가 재시도 전체(실패 1회 + 성공 1회)를 측정
        System.out.println(timed("retry-total", Fn.retry(3, flaky)).get());
        // 출력:   시도 1 실패: fail
        // 출력: [retry-total] 0 ms
        // 출력: ok
    }
}

문제 2

Discount에 새 조합 메서드 default Discount capped(long maxOff)를 추가하세요. 원래 정책의 할인액이 maxOff를 넘으면 maxOff까지만 할인합니다. 그리고 "30% 할인, 최대 5000원까지" 정책을 만들어 10,000원과 50,000원에 적용한 결과를 출력하세요. Discount 인터페이스 본문을 수정하지 않고 static 유틸 메서드로 같은 기능을 만드는 버전도 작성하세요.

정답 보기
java
public class Q2 {
    // 버전 A: 인터페이스에 default로 추가한다면
    // default Discount capped(long maxOff) {
    //     return p -> Math.max(this.apply(p), p - maxOff);
    // }

    // 버전 B: 인터페이스를 건드리지 않는 static 유틸
    static Discount capped(Discount d, long maxOff) {
        return p -> Math.max(d.apply(p), p - maxOff);      // 할인 후 가격이 (원가 - 상한)보다 낮으면 상한 적용
    }

    public static void main(String[] args) {
        Discount thirtyOffMax5000 = capped(Discount.rate(30), 5000);
        System.out.println(thirtyOffMax5000.apply(10_000));     // 출력: 7000   (30% = 3000원 할인, 상한 이내)
        System.out.println(thirtyOffMax5000.apply(50_000));     // 출력: 45000  (30% = 15000원이지만 5000원까지만)

        // 다른 정책과도 그대로 조합 가능
        Discount combo = thirtyOffMax5000.then(Discount.fixed(500));
        System.out.println(combo.apply(50_000));                // 출력: 44500
    }
}

문제 3

Stream.iterate를 사용해 콜라츠 수열(짝수면 n/2, 홀수면 3n+1, 1에서 종료)을 만드는 static List<Long> collatz(long start)를 작성하세요. 그리고 Fn.memoize를 이용해 "콜라츠 수열 길이"를 캐시하는 Function<Long, Integer>를 만들어, 같은 입력을 두 번 호출했을 때 계산이 한 번만 일어남을 카운터로 확인하세요.

정답 보기
java
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Stream;

public class Q3 {
    static List<Long> collatz(long start) {
        return Stream.iterate(start, n -> n != 1, n -> n % 2 == 0 ? n / 2 : 3 * n + 1)
                .toList();                                 // 1은 종료 조건이라 포함되지 않음
    }

    public static void main(String[] args) {
        System.out.println(collatz(6));                    // 출력: [6, 3, 10, 5, 16, 8, 4, 2]
        System.out.println(collatz(1));                    // 출력: []

        AtomicInteger calls = new AtomicInteger();
        Function<Long, Integer> length = Fn.memoize(n -> {
            calls.incrementAndGet();
            return collatz(n).size() + 1;                  // 마지막 1까지 세면 +1
        });
        System.out.println(length.apply(27L));             // 출력: 112
        System.out.println(length.apply(27L));             // 출력: 112
        System.out.println("계산 횟수: " + calls.get());    // 출력: 계산 횟수: 1
    }
}
연습 문제
  • 문제 1
  • 문제 2
  • 문제 3
이전 섹션4 자주 하는 실수 (Tip)5 / 6다음 섹션6 정리