Что делают методы 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 блокирует поток? — Нет, выполняется callback-ом при завершении 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. Что такое блокирующий код и как его отличить от неблокирующего]]