Gymterview
junior

Чем отличаются два интерфейса 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 и могут быть заданы лямбда-выражением.