← Назад к вопросам

Чем отличаются интерфейсы Runnable и Callable?

2.0 Middle🔥 201 комментариев
#SOLID и паттерны проектирования#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Различия между Runnable и Callable

Runnable и Callable — это два интерфейса для создания задач, которые можно выполнять в отдельных потоках. Они имеют важные различия.

Сравнительная таблица

ПараметрRunnableCallable
Методvoid run()V call() throws Exception
Возвращаемое значениеНет (void)Да (любого типа V)
ИсключенияНе может выбросить checked exceptionМожет выбросить checked exception
Интерфейс сВводится в JDK 1.0Вводится в JDK 1.5
ИспользованиеThread, ExecutorExecutorService, Future
Получение результатаСложно (нужны переменные класса)Просто (Future)

Runnable

Runnable — это интерфейс для задач, которые ничего не возвращают.

public interface Runnable {
    void run();
}

Особенности:

  • Метод run() не возвращает значения
  • Не может выбросить checked exception
  • Более простой, но менее гибкий
public class PrintTask implements Runnable {
    @Override
    public void run() {
        System.out.println("Работает Runnable");
        // Не может вернуть результат
    }
}

// Использование
Thread thread = new Thread(new PrintTask());
thread.start();

// Или с ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new PrintTask());
executor.shutdown();

Callable

Callable — это интерфейс для задач, которые возвращают результат.

public interface Callable<V> {
    V call() throws Exception;
}

Особенности:

  • Метод call() возвращает значение типа V
  • Может выбросить checked exception
  • Более мощный и гибкий
  • Работает с Future для получения результата
public class CalculateTask implements Callable<Integer> {
    private int a, b;
    
    public CalculateTask(int a, int b) {
        this.a = a;
        this.b = b;
    }
    
    @Override
    public Integer call() throws Exception {
        // Может выбросить исключение
        if (b == 0) {
            throw new IllegalArgumentException("b не может быть 0");
        }
        return a / b;  // Возвращаем результат
    }
}

// Использование
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(new CalculateTask(10, 2));

try {
    Integer result = future.get();  // получаем результат
    System.out.println("Результат: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    executor.shutdown();
}

Практические примеры

Пример 1: Runnable без результата

public class DataProcessor implements Runnable {
    private String data;
    
    public DataProcessor(String data) {
        this.data = data;
    }
    
    @Override
    public void run() {
        System.out.println("Обрабатываю: " + data);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Готово: " + data);
    }
}

// Использование
Thread t1 = new Thread(new DataProcessor("данные 1"));
Thread t2 = new Thread(new DataProcessor("данные 2"));
t1.start();
t2.start();

Пример 2: Callable с результатом

public class FileReader implements Callable<String> {
    private String filename;
    
    public FileReader(String filename) {
        this.filename = filename;
    }
    
    @Override
    public String call() throws Exception {
        // Может выбросить IOException
        java.nio.file.Path path = java.nio.file.Paths.get(filename);
        return new String(java.nio.file.Files.readAllBytes(path));
    }
}

// Использование
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<String> future = executor.submit(new FileReader("file.txt"));

try {
    String content = future.get();
    System.out.println("Содержимое файла: " + content);
} catch (ExecutionException e) {
    System.err.println("Ошибка чтения файла: " + e.getCause());
} finally {
    executor.shutdown();
}

Пример 3: Несколько Callable задач

public class SumCalculator implements Callable<Long> {
    private int start, end;
    
    public SumCalculator(int start, int end) {
        this.start = start;
        this.end = end;
    }
    
    @Override
    public Long call() throws Exception {
        long sum = 0;
        for (int i = start; i <= end; i++) {
            sum += i;
        }
        return sum;
    }
}

// Использование
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Long>> futures = new ArrayList<>();

// Создаем 4 задачи
futures.add(executor.submit(new SumCalculator(1, 25)));
futures.add(executor.submit(new SumCalculator(26, 50)));
futures.add(executor.submit(new SumCalculator(51, 75)));
futures.add(executor.submit(new SumCalculator(76, 100)));

// Собираем результаты
long totalSum = 0;
for (Future<Long> future : futures) {
    try {
        totalSum += future.get();  // ждем результата
    } catch (ExecutionException e) {
        System.err.println("Ошибка: " + e.getCause());
    }
}

System.out.println("Общая сумма: " + totalSum);  // 5050
executor.shutdown();

Обработка исключений

Runnable — проблемы с исключениями

public class BadRunnable implements Runnable {
    @Override
    public void run() {
        // ЭТО ОШИБКА: нельзя выбросить checked exception
        // throw new IOException("Ошибка");  // COMPILE ERROR!
        
        // Нужно обрабатывать внутри
        try {
            // код, который может выбросить IOException
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Callable — естественная обработка исключений

public class GoodCallable implements Callable<String> {
    @Override
    public String call() throws IOException {  // просто объявляем
        // код, который может выбросить IOException
        return "результат";
    }
}

// Обрабатываем при получении результата
try {
    String result = future.get();
} catch (ExecutionException e) {
    if (e.getCause() instanceof IOException) {
        System.err.println("Ошибка IO: " + e.getCause());
    }
}

Lambda выражения

С Java 8 оба интерфейса удобно использовать с лямбда-выражениями:

// Runnable с lambda
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> System.out.println("Runnable лямбда"));

// Callable с lambda
ExecutorService service = Executors.newSingleThreadExecutor();
Future<Integer> future = service.submit(() -> 10 + 20);

try {
    Integer result = future.get();  // 30
    System.out.println("Результат: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    service.shutdown();
}

Когда использовать что?

Используй Runnable если:

  • Задача ничего не возвращает
  • Нужна простая операция
  • Используешь Thread напрямую
  • Не нужно получать результат

Используй Callable если:

  • Нужно получить результат из задачи
  • Нужна обработка checked exception
  • Используешь ExecutorService
  • Нужно дождаться результата (Future.get())

Современный подход

Если возможно, используй Stream API или CompletableFuture вместо явного создания Runnable/Callable:

// Вместо явного Callable
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    return 10 + 20;
});

future.thenAccept(result -> System.out.println("Результат: " + result));

Это более удобно и мощно для сложных сценариев асинхронного программирования.