Питання 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]]