Что делает метод 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. Что такое блокирующий код и как его отличить от неблокирующего]]