← Назад к вопросам
Чем отличаются интерфейсы Runnable и Callable?
2.0 Middle🔥 201 комментариев
#SOLID и паттерны проектирования#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Различия между Runnable и Callable
Runnable и Callable — это два интерфейса для создания задач, которые можно выполнять в отдельных потоках. Они имеют важные различия.
Сравнительная таблица
| Параметр | Runnable | Callable |
|---|---|---|
| Метод | void run() | V call() throws Exception |
| Возвращаемое значение | Нет (void) | Да (любого типа V) |
| Исключения | Не может выбросить checked exception | Может выбросить checked exception |
| Интерфейс с | Вводится в JDK 1.0 | Вводится в JDK 1.5 |
| Использование | Thread, Executor | ExecutorService, 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));
Это более удобно и мощно для сложных сценариев асинхронного программирования.