Чем отличаются два интерфейса Runnable и Callable?
Оба интерфейса предназначены для описания задач, выполняемых в отдельном потоке, но имеют существенные различия.
Сигнатуры
Пример
// Java 1.0
@FunctionalInterface
public interface Runnable {
void run();
}
// Java 5.0 (java.util.concurrent)
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Сравнение
| Характеристика | Runnable |
Callable<V> |
|---|---|---|
| Появился в | Java 1.0 | Java 5.0 (java.util.concurrent) |
| Метод | run() |
call() |
| Возвращаемое значение | void – ничего не возвращает |
V – возвращает результат через Future<V> |
| Проверяемые исключения | Не может выбрасывать | Может выбрасывать Exception |
Использование с Thread |
new Thread(runnable) |
Нельзя напрямую – только через ExecutorService |
Использование с ExecutorService |
execute(runnable) или submit(runnable) |
submit(callable) |
Примеры использования
Пример: Runnable vs Callable
public class RunnableVsCallable {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
// Runnable: нет возвращаемого значения, нет checked exceptions
Runnable task1 = () -> {
System.out.println("Runnable выполняется");
// return "результат"; // Ошибка компиляции!
// throw new IOException(); // Ошибка компиляции!
};
Future<?> future1 = executor.submit(task1);
future1.get(); // null (Runnable ничего не возвращает)
// Callable: возвращает значение и может бросать checked exceptions
Callable<String> task2 = () -> {
if (someCondition()) {
throw new IOException("Ошибка ввода-вывода");
}
return "Результат вычисления";
};
Future<String> future2 = executor.submit(task2);
String result = future2.get(); // "Результат вычисления"
executor.shutdown();
}
}
Когда что использовать
Runnable– когда задача не возвращает результат и не нужно пробрасывать проверяемые исключения. Также используется при создании потоков черезnew Thread(runnable).Callable– когда нужен результат выполнения задачи или задача может выбросить проверяемое исключение. Всегда используется сExecutorService.
Конвертация Runnable в Callable
Утилитный метод Executors.callable(Runnable task, T result) позволяет обернуть Runnable в Callable:
Пример
Callable<String> wrapped = Executors.callable(runnable, "default");
Аналогия из жизни.
Runnable– это записка «Сходи в магазин», на которой нет места для ответа.Callable– это записка «Сходи в магазин и принеси чек», к которой приложен конверт для ответа (Future). Кроме того,Callableпозволяет написать «если магазин закрыт – сообщи» (checked exception), аRunnable– нет.
На собеседовании. Четыре ключевых отличия: (1)
Callableвозвращает результат черезFuture, (2)Callableможет бросать checked exceptions, (3)Callableпоявился в Java 5 вместе сjava.util.concurrent, (4)Callableнельзя передать в конструкторThreadнапрямую. Бонусный ответ: оба интерфейса являются@FunctionalInterfaceи могут быть заданы лямбда-выражением.