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

Можно ли вручную завершить CompletableFuture результатом

Также можно завершить с ошибкой:

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

🟢 Junior Level

Да! Метод complete() позволяет вручную завершить CompletableFuture:

CompletableFuture<String> cf = new CompletableFuture<>();

// Вручную завершить результатом
cf.complete("Hello");

// Теперь cf завершён
System.out.println(cf.isDone());  // true
System.out.println(cf.join());    // Hello

Также можно завершить с ошибкой:

cf.completeExceptionally(new RuntimeException("Error"));

🟡 Middle Level

complete()

CompletableFuture<String> cf = new CompletableFuture<>();

// Завершить
boolean completed = cf.complete("result");  // true

// Повторный complete игнорируется
completed = cf.complete("other");  // false
System.out.println(cf.join());  // "result" — первый побеждает

completeExceptionally()

cf.completeExceptionally(new RuntimeException("Error"));

// При попытке получить результат
try {
    cf.join();
} catch (CompletionException e) {
    System.out.println(e.getCause());  // RuntimeException: Error
}

obtrudeValue() — принудительное завершение

cf.complete("first");
cf.obtrudeValue("overridden");  // принудительно меняет

System.out.println(cf.join());  // "overridden"

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

  1. obtrudeValue без причины:
    // ⚠️ obtrudeValue нарушает контракт «однократного завершения» CompletableFuture.
    // Любой код, который уже получил результат и начал обработку, может работать
    // с устаревшими данными. Новый результат увидят только те, кто обратится к CF
    // ПОСЛЕ вызова obtrudeValue. В многопоточной среде — race conditions.
    cf.obtrudeValue("new value");
    

🔴 Senior Level

Internal Implementation

public boolean complete(T value) {
    boolean triggered = completeValue(value);
    if (triggered)
        postComplete();  // выполнить все callbacks
    return triggered;
}

public void obtrudeValue(T value) {
    result = (value == null) ? NIL : value;  // force set
}

Production Experience

Manual completion in tests:

@Test
void testManualCompletion() {
    CompletableFuture<String> cf = new CompletableFuture<>();
    
    // Имитация async завершения
    new Thread(() -> {
        Thread.sleep(100);
        cf.complete("done");
    }).start();
    
    assertEquals("done", cf.get(1, TimeUnit.SECONDS));
}

Best Practices

// ✅ complete() для ручного завершения
cf.complete(result);

// ✅ completeExceptionally() для ошибок
cf.completeExceptionally(error);

// ❌ obtrudeValue() без причины
// ❌ Игнорирование return value complete()

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

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

  • complete(value) — завершает CF, если ещё не завершён. Возвращает true/false
  • completeExceptionally(ex) — завершает CF с ошибкой
  • obtrudeValue(value) — принудительно меняет результат, нарушая контракт однократного завершения
  • Первый complete побеждает, повторные игнорируются
  • При завершении выполняются все pending callbacks (postComplete)

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

  • complete() дважды — что будет? — Первый true, второй false. Результат = первый
  • obtrudeValue vs complete? — obtrudeValue force set, даже если CF уже завершён. Dangerous
  • completeExceptionally + complete — что будет? — Зависит от тайминга. Кто первый — тот и результат
  • Зачем manual completion? — Tests, cache, conditional async, integration с blocking API

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

  • «complete() можно вызвать несколько раз» — только первый succeeds
  • «obtrudeValue — safe для production» — race conditions, нарушает однократное завершение
  • «completeExceptionally не выполняет callbacks» — выполняет, как и complete

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

  • [[3. Как создать CompletableFuture, который уже завершён с результатом]]
  • [[19. Можно ли повторно использовать один CompletableFuture в нескольких цепочках]]
  • [[17. Как отменить выполнение CompletableFuture]]
  • [[23. Как тестировать код с CompletableFuture]]