Що робить метод join() і чим він відрізняється від get()
У get() є версія з таймаутом: get(5, TimeUnit.SECONDS). У join() — НЕМАЄ таймаут-варіанту. У production-коді, де потрібен таймаут, використовуйте get() або обгортайте join() чер...
🟢 Junior Level
join() і get() — обидва очікують завершення CompletableFuture і повертають результат.
Головна відмінність:
get()— викидає checked виключення (InterruptedException,ExecutionException)join()— викидає unchecked (CompletionException)
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> "Hello");
// get() — потрібен try-catch
try {
String result = cf.get(); // throws checked exceptions
} catch (InterruptedException | ExecutionException e) {
// handle
}
// join() — простіше
String result = cf.join(); // throws CompletionException (unchecked)
🟡 Middle Level
Коли що використовувати
get() — коли потрібні checked виключення:
try {
String result = cf.get(5, TimeUnit.SECONDS); // з таймаутом
} catch (TimeoutException e) {
// timeout handling
} catch (InterruptedException e) {
// interrupt handling
}
Критична практична відмінність
У get() є версія з таймаутом: get(5, TimeUnit.SECONDS). У join() — НЕМАЄ таймаут-варіанту. У production-коді, де потрібен таймаут, використовуйте get() або обгортайте join() через orTimeout().
join() — для ланцюжків і тестів:
// У ланцюжках
CompletableFuture.allOf(cf1, cf2, cf3)
.thenApply(v -> {
String r1 = cf1.join(); // не потрібно try-catch
String r2 = cf2.join();
String r3 = cf3.join();
return combine(r1, r2, r3);
});
// В тестах
@Test
void test() {
String result = cf.join(); // простіше
assertEquals("expected", result);
}
Типові помилки
- join() без обробки помилок: ```java // ❌ Якщо CF впаде — CompletionException String result = cf.join(); // може викинути
// ✅ З обробкою cf.exceptionally(ex -> “fallback”) .join(); // безпечно
---
## 🔴 Senior Level
### Internal Implementation
```java
// Спрощене представлення. Реальні методи в JDK можуть відрізнятися.
public T get() throws InterruptedException, ExecutionException {
Object r = reportGet();
if (r instanceof AltResult alt) {
if (alt.ex != null) throw new ExecutionException((Throwable) alt.ex);
}
return (T) r;
}
public T join() {
Object r = reportGet();
if (r instanceof AltResult alt) {
if (alt.ex != null) throw new CompletionException((Throwable) alt.ex);
}
return (T) r;
}
// Різниця тільки в типі виключення!
Performance
get(): ~100 ns (checked exception handling)
join(): ~100 ns (same internals)
Різниця negligible — вибір за зручністю
Best Practices
// ✅ join() у ланцюжках
cf.thenApply(v -> cf2.join());
// ✅ join() в тестах
assertEquals("expected", cf.join());
// ✅ get(timeout) для production
cf.get(5, TimeUnit.SECONDS);
// ❌ join() без обробки помилок
// ❌ get() без таймауту в production
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- join() — unchecked CompletionException, get() — checked InterruptedException + ExecutionException
- join() НЕМАЄ таймауту, get(timeout, unit) Є таймаут
- join() перевагіший у ланцюжках і тестах, get(timeout) для production
- Внутрішньо однакові — різниця тільки в типі виключення
- Обидва блокують потік до завершення CF
Часті уточнюючі питання:
- join() або get() — коли що? — join() у ланцюжках/тестах, get(timeout) у production
- join() може викинути TimeoutException? — Ні. Потрібен orTimeout() перед join()
- CompletionException vs ExecutionException? — CompletionException (join) — unchecked, ExecutionException (get) — checked
- join() після allOf() блокує? — Ні, якщо allOf() завершений. Але join() без allOf() — блокує
Червоні прапорці (НЕ говорити):
- «join() неблокуючий» — блокує до завершення CF
- «get() і join() повністю ідентичні» — різні типи виключень, get має таймаут
- «join() без timeout у production — ок» — нескінченне очікування при помилці
Пов’язані теми:
- [[20. Як реалізувати timeout для CompletableFuture]]
- [[23. Як тестувати код з CompletableFuture]]
- [[6. Як обробляти виключення в ланцюжку CompletableFuture]]
- [[14. Що таке блокуючий код і як його відрізнити від неблокуючого]]