함수를 받아 "실행 시간을 측정하고 결과를 돌려주는" 데코레이터 static <T> Supplier<T> timed(String label, Supplier<T> action)을 작성하세요. 실행 후 "[label] N ms"를 출력하고(N은 System.nanoTime 차이를 ms로 변환, 정확한 수치는 무관) 원래 결과를 그대로 돌려줍니다.
그 다음 Fn.retry와 timed를 겹쳐서 적용해 보세요(재시도 전체 시간이 측정되어야 함).
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
}
}Discount에 새 조합 메서드 default Discount capped(long maxOff)를 추가하세요. 원래 정책의 할인액이 maxOff를 넘으면 maxOff까지만 할인합니다. 그리고 "30% 할인, 최대 5000원까지" 정책을 만들어 10,000원과 50,000원에 적용한 결과를 출력하세요. Discount 인터페이스 본문을 수정하지 않고 static 유틸 메서드로 같은 기능을 만드는 버전도 작성하세요.
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
}
}Stream.iterate를 사용해 콜라츠 수열(짝수면 n/2, 홀수면 3n+1, 1에서 종료)을 만드는 static List<Long> collatz(long start)를 작성하세요. 그리고 Fn.memoize를 이용해 "콜라츠 수열 길이"를 캐시하는 Function<Long, Integer>를 만들어, 같은 입력을 두 번 호출했을 때 계산이 한 번만 일어남을 카운터로 확인하세요.
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
}
}