Может ли Callable быть предпочтительным по сравнению с Runnable?

Я понял разницу между интерфейсом Runnable и Callable в Java. Из Java 1.5 дополнительные функции были добавлены в интерфейс Runnable и были вызваны Callable для обеспечения обратной совместимости.

Мои вопросы теперь в том, что у нас есть интерфейс Callable, мы всегда будем использовать это? Каковы варианты использования Callable и использования Runnable?

(Это - хорошая статья о том, какие различия между ними)

Ответы

Ответ 1

Оба имеют свои возможности, и оба они поддерживаются инфраструктурой Executor в java.util.concurrent. Runnable работает дольше, но он все еще используется и не обескуражен.

Вызываемые вызовы могут вызывать исключения и возвращаемые значения, что делает их лучшей абстракцией для задач, ориентированных на результат (например, выборка ресурса из сети, выполнение дорогостоящего вычисления значения и т.п.) [из Java Concurrency в практике Goetz, Bloch et. al., стандартная работа над Java concurrency].

Итак, если вы разрабатываете API, я бы предложил использовать Callables, когда это возможно. Если вы уверены, что задачи не возвратят значения и не будут генерировать исключения, тогда также будут использоваться Runnables. Здесь нет черных и белых, особенно потому, что Runnables можно легко обернуть в Callables и наоборот.

В качестве примечания обратите внимание на то, что ваша реализация Callable не должна объявлять throws Exception; тот факт, что сам Callable заявляет, что это только позволяет разработчикам бросить любые проверенные исключения. Абоненты вашего Callable, которые полагаются исключительно на интерфейс Callable, должны будут писать код обработки исключений. Также обратите внимание, что Callables не нужно возвращать значение; вы можете просто объявить свой Callable для возврата Void (с капиталом "V" ).

Ответ 2

IMHO, Runnable - лучший тип, который следует использовать при принятии в качестве аргумента функции, которая

  • не имеет возвращаемого значения, но только побочные эффекты
  • должен обрабатывать исключения, а не распространять их

Не забывайте, что Callable.call() выбрасывает исключение. Это означает, что, если вы принимаете аргумент Callable as, этот Callable может вызывать любое исключение, и вы должны иметь способ правильно обрабатывать их все. Если вы не можете этого сделать, лучше разрешить разработчику Callable обрабатывать исключение, как он хочет, и сделать аргумент Runnable, чтобы сделать это ясным.

Ответ 3

Использовать случай использования Callable: ScheduledExecutorService.scheduleAtFixedRate и scheduleWithFixedDelay принимает только Runnable.

Ответ 4

Я предпочитаю Callable s, но в тех редких случаях вам может понадобиться Callable для Runnable, вы можете легко реализовать их, добавив метод run(), подобный этому, вашему Callable.

public void run(){
    try{
        call();
    } catch (Exception e){
        e.printStackTrace(); // or however you log errors
    }
}

В Java 8 вы также можете сделать интерфейс, чтобы сделать то же самое для вас:

public interface CallableRunnable<T> extends Callable<T>, Runnable {
    default void run(){
        try {
            call();
        } catch (Exception e) {
            e.printStackTrace(); // or however you log errors
        }
    }
}

Затем вам просто нужно изменить что-либо, что implements Callable<T> на implements CallableRunnable<T>. Таким образом, ваши задания всегда могут вызываться любым методом, который требует. Конечно, если вам нужна специальная обработка ошибок, вы все равно можете переопределить метод run() для обработки исключений, созданных вашим методом call(). Вы даже можете реализовать метод для этого:

public interface CallableRunnable<T> extends Callable<T>, Runnable {
    default void run(){
        try {
            call();
        } catch (Exception e) {
            handleCallExceptions(e);
        }
    }

    default void handleCallExceptions(Exception e){
        e.printStackTrace();
    }
}

Тогда любая специальная обработка исключений должна реализовать собственный метод handleExceptions(Exception)... но вам не нужно, если вам это не нужно. Я предпочитаю это, потому что он позволяет вам иметь реализацию, использующую вашу структуру ведения журнала и т.д.