Может ли один класс реализовать оба интерфейса: Runnable и Callable?

Ответ

Да, может. Один класс может реализовать как Runnable, так и Callable<V>. Это два отдельных интерфейса для представления задач в многопоточности.

Различия интерфейсов: Критерий Runnable Callable<V>
Возвращаемое значение void Тип V (обобщенный)
Исключения Не может бросать проверяемые (checked) исключения Может бросать Exception
Запуск Передается в Thread или ExecutorService.execute() Передается в ExecutorService.submit()

Пример класса, реализующего оба интерфейса:

import java.util.concurrent.Callable;

public class DualTask implements Runnable, Callable<String> {
    private final String data;

    public DualTask(String data) { this.data = data; }

    @Override
    public void run() {
        // Логика для Runnable
        System.out.println("Processing (Runnable): " + data);
    }

    @Override
    public String call() throws Exception {
        // Логика для Callable, может вернуть результат и бросить исключение
        if (data == null) throw new IllegalArgumentException("Data is null");
        return "Processed (Callable): " + data.toUpperCase();
    }
}

Использование:

DualTask task = new DualTask("test");

// Как Runnable
new Thread(task).start();
// Или
executorService.execute(task);

// Как Callable
Future<String> future = executorService.submit(task); // Здесь task используется как Callable
String result = future.get(); // result = "Processed (Callable): TEST"

Важно: Класс FutureTask (часто используемая обертка) может принимать Callable в конструкторе и сам реализует Runnable, но это не стандартный класс, реализующий оба интерфейса "напрямую".

Ответ 18+ 🔞

Да ты посмотри, какая хуйня творится в этих ваших многопоточках! Сидят два интерфейса, Runnable и Callable<V>, как два соседа по коммуналке — вроде оба задачи представляют, а поди разбери, кто из них что умеет.

Так вот, слушай сюда, ёпта. Класс-то твой запросто может быть такой хитрожопой мартышкой, которая и Runnable реализует, и Callable<V> впридачу. Это как взять и впендюрить в один файл две разные сущности. Главное — не перепутать, где у тебя логика для потока без возврата, а где для потока с результатом.

Смотри, в чём разница-то, а то потом будешь ебаться как Герасим с Муму:

  • Runnable — это как немой работяга, блядь. Сделал дело — и молча в сторону. Ни хуя не вернёт (void), и проверяемые исключения выкинуть не может. Кинул его в new Thread() или executorService.execute() — и поехали.
  • Callable<V> — это уже более говорящий товарищ. Он может вернуть результат какого-то типа V и, что важно, имеет право заорать Exception, если что-то пошло не так. Его только через executorService.submit() запускают, чтобы потом за результат ухватиться.

Вот тебе живой пример, смотри, не обосрись:

import java.util.concurrent.Callable;

public class UniversalSoldier implements Runnable, Callable<String> {
    private final String payload;

    public UniversalSoldier(String payload) { this.payload = payload; }

    @Override
    public void run() {
        // Типа работа для Runnable — просто делаем и молчим
        System.out.println("Безвозвратно обрабатываю: " + payload);
    }

    @Override
    public String call() throws Exception {
        // А тут работа для Callable — можем и результат дать, и исключение швырнуть
        if (payload == null) throw new IllegalArgumentException("Да мне null подсунули, ёбта!");
        return "Обработанный результат: " + payload.toUpperCase();
    }
}

Использовать эту двуличную сущность можно так:

UniversalSoldier soldier = new UniversalSoldier("задание");

// Запускаем как немого Runnable
new Thread(soldier).start();
// Или так
executorService.execute(soldier);

// А теперь используем как болтливого Callable
Future<String> future = executorService.submit(soldier); // Здесь солдат уже как Callable работает
String итог = future.get(); // Получим: "Обработанный результат: ЗАДАНИЕ"

Вот и вся магия, блядь. Главное — головой думай, когда какой метод вызываешь, а то получишь либо нихуя, либо неожиданный пиздец с исключением.