Вопрос 2 · Раздел 19

Какие основные преимущества CompletableFuture перед Future

CompletableFuture имеет 5 главных преимуществ перед обычным Future:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

CompletableFuture имеет 5 главных преимуществ перед обычным Future:

  1. Не блокирует — можно строить цепочки без ожидания
  2. Комбинирование — можно объединять несколько CompletableFuture
  3. Обработка ошибок — встроенные методы для исключений
  4. Создание вручную — можно завершить результат в любой момент
  5. Больше методов — 40+ методов для разных сценариев
// Future — только ждать
Future<String> future = executor.submit(() -> "Hello");
String result = future.get();  // блокирует!

// CompletableFuture — цепочка без ожидания
CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenAccept(System.out::println);  // не блокирует!

🟡 Middle Level

Детальные преимущества

1. Composability (Компонуемость):

// Future — нельзя комбинировать
Future<Integer> f1 = executor.submit(() -> 10);
Future<Integer> f2 = executor.submit(() -> 20);
// Нужно ждать оба и складывать вручную

// CompletableFuture — thenCombine
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> 20);

cf1.thenCombine(cf2, (a, b) -> a + b)
   .thenAccept(System.out::println);  // 30

2. Обработка ошибок:

// Future — try/catch вручную
try {
    Integer result = future.get();
} catch (ExecutionException e) {
    // manual handling
}

// CompletableFuture — exceptionally
cf.exceptionally(ex -> {
    log.error("Error", ex);
    return defaultValue;
});

3. Manual completion:

// Можно завершить вручную
CompletableFuture<String> cf = new CompletableFuture<>();

// В другом потоке:
cf.complete("result");     // установить результат
cf.completeExceptionally(new RuntimeException("Error"));  // ошибку

4. Async chain без блокировки:

// Future — блокирует на каждом шаге
String url = future1.get();
String data = future2.get();
Result result = process(url, data);

// CompletableFuture — вся цепочка асинхронная
supplyAsync(() -> getUrl())
    .thenCompose(url -> fetchDataAsync(url))
    .thenApply(data -> processData(data))
    .thenAccept(result -> save(result));

5. Timeout поддержка:

// Java 9+
cf.orTimeout(5, TimeUnit.SECONDS);
cf.completeOnTimeout(defaultValue, 5, TimeUnit.SECONDS);

Типичные ошибки

  1. Не используют async методы: ```java // ❌ Выполняется в том же потоке cf.thenApply(s -> s.toUpperCase());

// ✅ Выполняется асинхронно cf.thenApplyAsync(s -> s.toUpperCase(), executor);

// thenApply в том же потоке — это НЕ баг, а intentional design.
// Используйте для лёгких трансформаций. Для blocking — только *Async.

---

## 🔴 Senior Level

### Internal Implementation

**CompletionStage interface:**
```java
// CompletableFuture implements CompletionStage<T>
// CompletionStage предоставляет 40+ методов:
// - thenApply, thenAccept, thenRun
// - thenCombine, thenCompose
// - allOf, anyOf
// - exceptionally, handle, whenComplete

// Каждый метод возвращает CompletionStage — можно строить цепочки

Non-blocking execution:

// CompletableFuture использует callback-based подход
// Вместо blocking get() — callback выполняется при завершении

class CompletableFuture<T> {
    volatile Object result;
    volatile Completion stack;  // linked list of callbacks
    
    // При завершении — все callbacks выполняются
    void completeValue(T t) {
        // CAS для установки result
        // Потом выполняются все pending callbacks
    }
}

Архитектурные Trade-offs

Подход Плюсы Минусы
CompletableFuture Неблокирующий, композируемый Сложнее дебажить
Future + get() Простой Блокирующий
Reactive Streams Backpressure, streaming Сложнее
Virtual Threads Простой код, non-blocking Java 21+

Edge Cases

1. Exception wrapping:

// Исключения оборачиваются в CompletionException
cf.thenApply(s -> {
    throw new RuntimeException("Original");
}).exceptionally(ex -> {
    // ex — CompletionException
    // ex.getCause() — оригинальное исключение
    return null;
});

2. Cancellation:

// CompletableFuture.cancel() не прерывает выполнение
cf.cancel(true);  // mayInterruptIfRunning игнорируется

// Нужно обрабатывать вручную
cf.obtrudeValue(null);  // принудительно завершить

Производительность

// Порядок величин (зависит от JVM и workload):
// Callback: наносекунды, Async: микросекунды, Blocking: миллисекунды+

Production Experience

Microservice communication:

@Service
public class OrderService {
    private final UserService userService;
    private final InventoryService inventoryService;
    private final PaymentService paymentService;
    
    public CompletableFuture<Order> createOrder(CreateOrderRequest req) {
        return userService.validateUserAsync(req.userId())
            .thenCompose(user -> 
                inventoryService.checkStockAsync(req.items())
                    .thenCompose(stock -> 
                        paymentService.processPaymentAsync(req.payment())
                            .thenApply(payment -> 
                                new Order(user, stock, payment)
                            )
                    )
            )
            .exceptionally(ex -> {
                log.error("Order creation failed", ex);
                throw new OrderCreationException(ex);
            });
    }
}

Best Practices

// ✅ Цепочки вместо блокировки
cf.thenApply(...).thenAccept(...);

// ✅ Обработка ошибок
cf.exceptionally(ex -> defaultValue);

// ✅ Свой Executor для I/O
CompletableFuture.supplyAsync(task, ioExecutor);

// ✅ Комбинирование
cf1.thenCombine(cf2, (a, b) -> combine(a, b));

// ❌ Блокирующий get() без причины
// ❌ Игнорирование исключений
// ❌ commonPool для I/O операций

Когда НЕ использовать CompletableFuture

  • Simple fire-and-forget — достаточно executor.submit()
  • Virtual Threads (Java 21+) — часто проще писать синхронный код
  • Стримы данных — лучше reactive (Flux/Mono)

🎯 Шпаргалка для интервью

Обязательно знать:

  • Неблокирующая композиция — цепочки без блокировки get()
  • Комбинирование — thenCombine, allOf, anyOf для параллельных вызовов
  • Обработка ошибок — exceptionally, handle, whenComplete
  • Manual completion — complete(), completeExceptionally()
  • 40+ методов CompletionStage для любых сценариев
  • Timeout поддержка — orTimeout(), completeOnTimeout() (Java 9+)

Частые уточняющие вопросы:

  • Главное преимущество перед Future? — Неблокирующая композиция и обработка ошибок без get()
  • Как комбинировать два CF? — thenCombine(cf2, combiner) для параллельных, thenCompose для зависимых
  • Когда НЕ использовать? — Simple fire-and-forget, Virtual Threads (Java 21+), стримы данных (лучше Reactor)
  • Как избежать блокировки? — Цепочки thenApply/thenAccept вместо get()/join()

Красные флаги (НЕ говорить):

  • «Future и CompletableFuture interchangeable» — у Future нет цепочек и обработки ошибок
  • «thenCompose и thenCombine это одно и то же» — thenCompose последовательный, thenCombine параллельный
  • «Всегда использую commonPool» — для I/O это thread pool starvation

Связанные темы:

  • [[1. Что такое CompletableFuture и чем он отличается от Future]]
  • [[4. В чём разница между thenApply() и thenCompose()]]
  • [[8. Как комбинировать результаты нескольких CompletableFuture]]
  • [[24. В каких случаях лучше использовать CompletableFuture, а в каких - реактивное программирование]]