Що роблять методи thenAccept() і thenRun()
Обидва методи використовуються для завершення ланцюжка CompletableFuture:
🟢 Junior Level
Обидва методи використовуються для завершення ланцюжка CompletableFuture:
thenAccept()— приймає результат і споживає його (не повертає нічого)thenRun()— просто виконує дію, не знаючи про результат
// thenAccept — отримує результат
CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(s -> System.out.println("Got: " + s)); // Got: Hello
// thenRun — не отримує результат
CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("Done!")); // Done!
Проста аналогія:
thenAccept— отримати посилку і відкрити їїthenRun— просто знати, що посилка доставлена
🟡 Middle Level
Детальне порівняння
**thenAccept — Consumer
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "Hello");
// Отримуємо результат
cf.thenAccept(result -> {
System.out.println("Result: " + result);
saveToDatabase(result);
});
// Тип: CompletableFuture<Void> — не можна продовжити ланцюжок зі значенням
thenRun — Runnable:
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "Hello");
// Просто дія після завершення
cf.thenRun(() -> {
System.out.println("Task completed");
notifyUser();
});
Коли що використовувати
thenAccept:
// Коли потрібен результат
fetchDataAsync()
.thenAccept(data -> {
process(data);
saveToCache(data);
});
thenRun:
// Коли результат не важливий
sendEmailAsync(email)
.thenRun(() -> log.info("Email sent"));
// Або для side effects
saveToDatabase(entity)
.thenRun(() -> metrics.increment("saved"));
Типові помилки
- Спроба отримати результат у thenRun:
```java
CompletableFuture
cf = CompletableFuture.supplyAsync(() -> "Hello");
// ❌ thenRun не отримує результат cf.thenRun(() -> { // System.out.println(result); // result недоступний! System.out.println(“Done”); });
// ✅ thenAccept отримує результат cf.thenAccept(result -> System.out.println(result));
---
## 🔴 Senior Level
### Internal Implementation
**thenApply vs thenAccept vs thenRun:**
```java
public <U> CompletableFuture<U> thenApply(Function<T, U> fn) {
// fn: T -> U
// Повертає CompletableFuture<U>
}
public CompletableFuture<Void> thenAccept(Consumer<T> action) {
// action: T -> void
// Повертає CompletableFuture<Void>
}
public CompletableFuture<Void> thenRun(Runnable action) {
// action: () -> void
// Повертає CompletableFuture<Void>
}
Void result:
// thenAccept і thenRun повертають CompletableFuture<Void>
// Але можна продовжити ланцюжок:
cf.thenAccept(result -> process(result))
.thenRun(() -> notify()); // OK
cf.thenRun(() -> log.info("Done"))
.thenAccept(v -> { }); // v — Void, марно
Архітектурні Trade-offs
| Метод | Отримує результат | Повертає | Use case |
|---|---|---|---|
| thenApply | ✅ T | U | Трансформація |
| thenAccept | ✅ T | Void | Споживання |
| thenRun | ❌ | Void | Side effect |
Edge Cases
1. Exception handling:
// Виключення в thenAccept:
cf.thenAccept(result -> {
throw new RuntimeException("Error processing");
}).exceptionally(ex -> {
// Спіймає виключення
return null;
});
// thenRun теж ловить виключення
cf.thenRun(() -> {
throw new RuntimeException("Error");
}).exceptionally(ex -> {
// Спіймає
return null;
});
2. Async версії:
// thenAcceptAsync — виконується у ForkJoinPool
cf.thenAcceptAsync(result -> process(result));
// З своїм Executor
cf.thenAcceptAsync(result -> process(result), executor);
// thenRunAsync — аналогічно
cf.thenRunAsync(() -> notify(), executor);
Продуктивність
thenApply: ~5 ns (функція)
thenAccept: ~5 ns (consumer)
thenRun: ~3 ns (runnable — немає доступу до T)
thenAcceptAsync / thenRunAsync:
- + ~1μs на переключення потоку
Production Experience
Logging and metrics:
@Service
public class OrderService {
public CompletableFuture<Order> createOrder(OrderRequest req) {
return orderRepository.saveAsync(req)
.thenApply(order -> {
metrics.increment("orders.created");
eventPublisher.publish(new OrderCreatedEvent(order));
return order; // thenApply повертає результат — order в scope
});
}
// thenRun для side effects
public CompletableFuture<Void> sendNotification(Order order) {
return notificationClient.sendAsync(order)
.thenRun(() -> log.info("Notification sent for order {}", order.id()));
}
}
Best Practices
// ✅ thenAccept для споживання результату
cf.thenAccept(result -> process(result));
// ✅ thenRun для side effects
cf.thenRun(() -> metrics.increment("done"));
// ✅ Async версії з Executor
cf.thenAcceptAsync(result -> process(result), executor);
// ❌ thenRun коли потрібен результат
// ❌ thenAccept коли результат не потрібен
// ❌ Блокуючі операції в thenAccept/thenRun
Коли НЕ використовувати thenRun
Коли результат попереднього етапу потрібен для подальшої обробки. thenRun = “зроби щось після, але результат мені не важливий”.
// ❌ thenRun — order недоступний
orderRepository.saveAsync(req)
.thenRun(() -> notifyUser(order.id())); // compilation error: order not found
// ✅ thenAccept — order доступний
orderRepository.saveAsync(req)
.thenAccept(order -> notifyUser(order.id()));
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- thenAccept(Consumer
) — отримує результат T, повертає CompletableFuture - thenRun(Runnable) — НЕ отримує результат, повертає CompletableFuture
- thenAccept для споживання результату, thenRun для side effects (логування, метрики)
- Обидва мають Async версії з переключенням потоку
- thenApply vs thenAccept: thenApply повертає U, thenAccept повертає Void
Часті уточнюючі питання:
- Коли thenAccept, а коли thenRun? — thenAccept коли потрібен результат попереднього CF, thenRun коли результат не важливий
- Чи можна продовжити ланцюжок після thenAccept? — Так, але наступна ланка отримує Void
- thenAccept блокує потік? — Ні, виконується колбеком при завершенні CF
- thenAccept vs thenApply — коли що? — thenApply для трансформації (повертає U), thenAccept для споживання (повертає Void)
Червоні прапорці (НЕ говорити):
- «thenRun отримує результат попереднього CF» — thenRun використовує Runnable, без параметрів
- «thenAccept завершує ланцюжок назавжди» — можна продовжити, але з Void
- «thenAcceptAsync завжди кращий thenAccept» — Async додає ~1μs overhead, для легких операцій надмірний
Пов’язані теми:
- [[4. У чому різниця між thenApply() і thenCompose()]]
- [[6. Як обробляти виключення в ланцюжку CompletableFuture]]
- [[11. У чому різниця між thenApply() і thenApplyAsync()]]
- [[14. Що таке блокуючий код і як його відрізнити від неблокуючого]]