if-else 사다리를 Map<Type, Function> 으로 바꿉니다. 새 결제 수단은 map 에 한 줄 추가면 끝입니다.
import java.util.*;
import java.util.function.*;
public class FeeStrategy {
enum PayType { CARD, BANK, POINT, KAKAO }
static final Map<PayType, LongUnaryOperator> FEE = new EnumMap<>(Map.<PayType, LongUnaryOperator>of(
PayType.CARD, amt -> amt * 3 / 100, // 3%
PayType.BANK, amt -> Math.min(500, amt / 100), // 1%, 최대 500
PayType.POINT, amt -> 0, // 무료
PayType.KAKAO, amt -> amt * 25 / 1000 + 100 // 2.5% + 100원
));
static long fee(PayType type, long amount) {
return FEE.getOrDefault(type, amt -> { throw new IllegalArgumentException("unsupported"); })
.applyAsLong(amount);
}
public static void main(String[] args) {
for (PayType t : PayType.values())
System.out.printf("%-5s 10,000원 → 수수료 %d원%n", t, fee(t, 10_000));
}
}
// 출력:
// CARD 10,000원 → 수수료 300원
// BANK 10,000원 → 수수료 100원
// POINT 10,000원 → 수수료 0원
// KAKAO 10,000원 → 수수료 350원주문 검증 규칙을 Predicate<Order> 와 메시지의 쌍으로 두고, and 로 합성하거나 하나씩 돌려 실패 사유를 모읍니다.
import java.util.*;
import java.util.function.Predicate;
import static java.util.function.Predicate.not;
public class ValidationChain {
record Order(String id, long amount, String email, List<String> items) {}
record Rule(String message, Predicate<Order> check) {}
static final List<Rule> RULES = List.of(
new Rule("금액은 양수", o -> o.amount() > 0),
new Rule("이메일 형식", o -> o.email() != null && o.email().contains("@")),
new Rule("상품 1개 이상", not(o -> o.items().isEmpty())),
new Rule("주문번호는 ORD- 로 시작", o -> o.id().startsWith("ORD-"))
);
static List<String> violations(Order o) {
List<String> out = new ArrayList<>();
for (Rule r : RULES) if (!r.check().test(o)) out.add(r.message());
return out;
}
// 모든 규칙을 하나의 Predicate 로 합성
static final Predicate<Order> ALL_VALID =
RULES.stream().map(Rule::check).reduce(o -> true, Predicate::and);
public static void main(String[] args) {
Order good = new Order("ORD-1", 5000, "a@b.com", List.of("pen"));
Order bad = new Order("X-1", -10, "nomail", List.of());
System.out.println(ALL_VALID.test(good) + " " + violations(good));
System.out.println(ALL_VALID.test(bad) + " " + violations(bad));
}
}
// 출력:
// true []
// false [금액은 양수, 이메일 형식, 상품 1개 이상, 주문번호는 ORD- 로 시작]this 차이 실험effectively final 위반이 어디서 나는지, 람다와 익명 클래스의 this 가 무엇을 가리키는지 확인합니다.
import java.util.function.*;
public class CaptureDemo {
String name = "outer";
void run() {
int base = 10; // effectively final
IntUnaryOperator addBase = x -> x + base;
// base = 20; // 주석 해제 시 위 람다에서 컴파일 에러
int[] counter = {0}; // 배열 요소 변경은 허용 (변수 재대입이 아님)
Runnable inc = () -> counter[0]++;
inc.run(); inc.run();
Supplier<String> lambdaThis = () -> this.name; // this = CaptureDemo 인스턴스
Supplier<String> anonThis = new Supplier<>() {
String name = "anon";
public String get() { return this.name; } // this = 익명 클래스 인스턴스
};
System.out.println(addBase.applyAsInt(5));
System.out.println(counter[0]);
System.out.println(lambdaThis.get() + " / " + anonThis.get());
// 캡처 없는 람다는 같은 인스턴스가 재사용될 수 있다 (구현 세부, 의존 금지)
Supplier<Integer> a = () -> 1, b = () -> 1;
System.out.println(a == b); // 서로 다른 람다 식이라 false
Runnable r1 = CaptureDemo::noop, r2 = CaptureDemo::noop;
System.out.println(r1.getClass() == r2.getClass());// 같은 식이 아니어도 클래스는 다를 수 있음
}
static void noop() {}
public static void main(String[] args) { new CaptureDemo().run(); }
}
// 출력:
// 15
// 2
// outer / anon
// false
// false결제 서비스가 완료 콜백(Consumer)과 실패 콜백을 받고, 여러 핸들러를 andThen 으로 묶어 등록합니다.
import java.util.*;
import java.util.function.*;
public class EventHandlers {
record Payment(String orderId, long amount) {}
static class PaymentService {
private Consumer<Payment> onSuccess = p -> {}; // 초기값: 아무것도 안 함
private BiConsumer<Payment, Exception> onFailure = (p, e) -> {};
void addSuccessHandler(Consumer<Payment> h) { onSuccess = onSuccess.andThen(h); }
void addFailureHandler(BiConsumer<Payment, Exception> h) { onFailure = onFailure.andThen(h); }
void pay(Payment p) {
try {
if (p.amount() <= 0) throw new IllegalArgumentException("금액 오류: " + p.amount());
onSuccess.accept(p);
} catch (Exception e) {
onFailure.accept(p, e);
}
}
}
public static void main(String[] args) {
List<String> audit = new ArrayList<>();
PaymentService svc = new PaymentService();
svc.addSuccessHandler(p -> System.out.println("영수증 발송: " + p.orderId()));
svc.addSuccessHandler(p -> audit.add("PAID " + p.orderId()));
svc.addSuccessHandler(p -> System.out.println("포인트 적립: " + p.amount() / 100));
svc.addFailureHandler((p, e) -> audit.add("FAIL " + p.orderId() + " " + e.getMessage()));
svc.pay(new Payment("ORD-1", 25_000));
svc.pay(new Payment("ORD-2", 0));
System.out.println(audit);
}
}
// 출력:
// 영수증 발송: ORD-1
// 포인트 적립: 250
// [PAID ORD-1, FAIL ORD-2 금액 오류: 0]주문 목록을 다단계 정렬하고, 설정에 따라 켜지는 후처리 단계를 Function 리스트로 접어 하나의 파이프라인으로 만듭니다.
import java.util.*;
import java.util.function.*;
import static java.util.Comparator.*;
public class Pipelines {
record Order(String id, String status, long amount, String customer) {}
public static void main(String[] args) {
List<Order> orders = new ArrayList<>(List.of(
new Order("O3", "PAID", 3000, "kim"),
new Order("O1", "PAID", 9000, "lee"),
new Order("O2", "PENDING", 9000, "kim"),
new Order("O4", "PAID", 9000, "kim")
));
// 상태 오름차순 → 금액 내림차순 → id 오름차순
orders.sort(comparing(Order::status)
.thenComparing(Order::amount, reverseOrder())
.thenComparing(Order::id));
orders.forEach(o -> System.out.println(o.status() + " " + o.amount() + " " + o.id()));
// 문자열 파이프라인: 단계 목록을 하나의 Function 으로 합성
List<UnaryOperator<String>> steps = List.of(String::strip, String::toLowerCase, s -> s.replace(' ', '-'));
Function<String, String> slugify = steps.stream()
.map(s -> (Function<String, String>) s)
.reduce(Function.identity(), Function::andThen);
System.out.println(slugify.apply(" Hello Lambda World "));
// compose vs andThen
Function<Integer, Integer> twice = x -> x * 2, plus3 = x -> x + 3;
System.out.println(twice.andThen(plus3).apply(5) + " " + twice.compose(plus3).apply(5));
}
}
// 출력:
// PAID 9000 O1
// PAID 9000 O4
// PAID 3000 O3
// PENDING 9000 O2
// hello-lambda-world
// 13 16