62
задан 9 September 2019 в 05:44

2 ответа

Причина этого, то, что эти вызовы на самом деле, вызовы к двум различным перегруженным методам, доступным в ExecutorService; каждый из этих методов, берущих отдельный аргумент различных типов:

  1. <T> Future<T> submit(Callable<T> task);
  2. Future<?> submit(Runnable task);

Затем то, что происходит, - то, что компилятор преобразовывает лямбду в первом случае Вашей проблемы в Callable<?> функциональный интерфейс (вызов первого перегруженного метода); и во втором случае Вашей проблемы преобразовывает лямбду в Runnable функциональный интерфейс (вызывающий поэтому второй перегруженный метод), требуя из-за этого для обработки Exception брошенный; но не в предыдущем случае с помощью Callable.

, Хотя оба функциональных интерфейса не берут аргументов, Callable<?> , возвращает значение :

  1. Вызываемый: V call() throws Exception;
  2. Выполнимый: public abstract void run();

, Если мы переключаемся на примеры, которые обрезают код к соответствующим частям (для легкого исследования просто любопытных битов) затем, мы можем записать, эквивалентно к исходным примерам:

    ExecutorService executor = Executors.newSingleThreadExecutor();

    // LAMBDA COMPILED INTO A 'Callable<?>'
    executor.submit(() -> {
        while (true)
            throw new Exception();
    });

    // LAMBDA COMPILED INTO A 'Runnable': EXCEPTIONS MUST BE HANDLED BY LAMBDA ITSELF!
    executor.submit(() -> {
        boolean value = true;
        while (value)
            throw new Exception();
    });

С этими примерами, может быть легче заметить, что причина, почему первый преобразовывается в Callable<?>, в то время как второй преобразовывается в Runnable, из-за [1 130] выводы компилятора .

В обоих случаях, тела лямбды совместимы с пустотой , так как каждый оператор возврата в блоке имеет форму return;.

Теперь, в первом случае, компилятор делает следующее:

  1. Обнаруживает, что все пути выполнения в лямбде объявляют бросок контролируемые исключительные ситуации (с этого времени, мы будем относиться как [1 131] 'исключение' , подразумевая только [1 132] 'контролируемые исключительные ситуации' ). Это включает вызов любого метода, объявляющего выдавание исключения и явный вызов к [1 114].
  2. Приходит к заключению правильно, что ЦЕЛЫЙ тело лямбды эквивалентно блоку кода, объявляющему выдавание исключения; которым, конечно ДОЛЖЕН быть также: обработанный или повторно брошенный.
  3. , Так как лямбда не обрабатывает исключение, затем значения по умолчанию компилятора, чтобы предположить, что они исключение (исключения) должны быть повторно брошены.
  4. Безопасно выводит, что эта лямбда должна соответствовать функциональному интерфейсу, не может complete normally и поэтому совместимы со значением .
  5. С тех пор Callable<?> и Runnable потенциальные соответствия для этой лямбды, компилятор выбирает самое определенное соответствие (для покрытия всех сценариев); который является эти Callable<?>, преобразовывая лямбду в экземпляр его и создавая ссылку вызова на submit(Callable<?>) перегруженный метод.

, В то время как во втором случае компилятор делает следующее:

  1. Обнаруживает, что могут быть пути выполнения в лямбде, которые НЕ ДЕЛАЮТ , объявляют выдавание исключения (в зависимости от [1 133] оцененная будущим образом логика ).
  2. С тех пор не все пути выполнения объявляют выдавание исключения, компилятор приходит к заключению, что тело лямбды НЕ ОБЯЗАТЕЛЬНО эквивалентно блоку кода, объявляющему выдающий исключения - компилятор не заботится/платит о внимании, если некоторые части кода действительно объявляют, что они могут, только если целое тело делает или нет.
  3. Безопасно выводит, что лямбда не совместима со значением ; начиная с него МОЖЕТ complete normally.
  4. Выбирает Runnable (поскольку это - единственное доступное установка функциональный интерфейс для лямбды, которая будет преобразована в), и создает ссылку вызова на submit(Runnable) перегруженный метод. Все это прибытие в цену делегирования пользователю, ответственности обработки любого Exception с, брошенная везде, где они МОГУТ , происходит в частях тела лямбды.

Это было большим вопросом - я хорошо провел время, упорно ища его, Спасибо!

63
ответ дан 31 October 2019 в 14:29

Кратко

ExecutorService имеет и submit(Callable) и submit(Runnable) методы.

  1. В первом случае (с while (true)), и submit(Callable) и submit(Runnable) соответствие, таким образом, компилятор должен выбрать между ними
    • submit(Callable), предпочтено submit(Runnable), потому что Callable более конкретны , чем [1 111]
    • Callable, имеет throws Exception в [1 114], таким образом, не необходимо поймать исключение в нем
  2. Во втором случае (с while (tasksObserving)) только [1 116] соответствие, таким образом, компилятор выбирает, это
    • Runnable не имеет никакого throws объявление по run() метод, таким образом, это - ошибка компиляции для не ловли исключения в run() метод.

полная история

Спецификация языка Java описывает, как метод выбран во время компиляции программы в [1 156] 15.2.2$ :

  1. Определяют Потенциально Применимые Методы ( 15.12.2.1$ ), который сделан в 3 фазах для строгого, свободного и переменного вызова арности
  2. , Выбирают Most Specific Method ( 15.12.2.5$ ) из методов, найденных на первом шаге.

Позволяют нам проанализировать ситуацию с 2 submit() методы в двух фрагментах кода, обеспеченных OP:

ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        while(true)
        {
            //DO SOMETHING
            Thread.sleep(5000);
        }
    });

и

ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        while(tasksObserving)
        {
            //DO SOMETHING
            Thread.sleep(5000);
        }
    });

(где tasksObserving не последняя переменная).

Определяют Потенциально Применимые Методы

Первый, компилятор должен определить потенциально применимые методы : 15.12.2.1$

, Если участник является фиксированным методом арности с арностью n, арностью вызова метода, равны n, и для всего, что я (1 в‰ i ¤ в‰ ¤ n), i'th аргумент вызова метода потенциально совместим , как определено ниже, с типом i'th параметра метода.

и немного далее в том же разделе

выражение потенциально совместимо с целевым типом согласно следующим правилам:

лямбда-выражение А (В§15.27) потенциально совместимо с функциональным интерфейсным типом (В§9.8), если все следующее верно:

арность функционального типа целевого типа совпадает с арностью лямбда-выражения.

, Если функциональный тип целевого типа имеет пустой возврат, то тело лямбды является или выражением оператора (В§14.8) или совместимым с пустотой блоком (В§15.27.2).

, Если функциональный тип целевого типа имеет (непустой) тип возврата, то тело лямбды является или выражением или совместимым со значением блоком (В§15.27.2).

Позволяют нам отметить, что в обоих случаях, лямбда является лямбдой блока.

Позволяют нам также отметить, что Runnable имеет void тип возврата, таким образом, чтобы быть потенциально совместима с [1 125], лямбда блока должна быть совместимый с пустотой блок . В то же время, Callable имеет непустой тип возврата, так чтобы быть потенциально comtatible с [1 127], лямбда блока должна быть совместимый со значением блок .

15.27.2$ определяют, каков пустой совместимый блок и совместимый блок значения .

тело лямбды блока А совместимо с пустотой, если каждый оператор возврата в блоке имеет форму return;.

тело лямбды блока А совместимо со значением, если оно не может обычно завершаться (В§14.21), и каждый оператор возврата в блоке имеет форму return Expression;.

Позволяют нам посмотреть на 14,21$, абзац приблизительно [1 130] цикл:

Некоторое время оператор может обычно завершать эквивалентность, по крайней мере одно из следующего верно:

, в то время как оператор достижим и выражение условия не является константным выражением (В§15.28) с верным значением.

существует достижимый оператор завершения, который выходит в то время как оператор.

В borh случаях, лямбды являются на самом деле лямбдами блока.

В первом случае, как это видно, существует while цикл с константным выражением со значением true (без [1 133] операторы), таким образом, это не может обычно завершаться (на 14,21$); также это не имеет никаких операторов возврата, следовательно первая лямбда совместима со значением .

В то же время, нет никаких return операторы вообще, таким образом, это также совместимо с пустотой . Так, в конце, в первом случае, лямбда и пусто - и совместима со значением .

Во втором случае, while цикл может обычно завершаться с точки зрения компилятора (потому что выражение цикла больше не является константным выражением), таким образом, лямбда в целом может обычно завершаться , таким образом, это не совместимый со значением блок . Но это - все еще совместимый с пустотой блок , потому что это не содержит никакой return операторы.

промежуточный результат состоит в том, что в первом случае лямбда и совместимый с пустотой блок и совместимый со значением блок ; во втором случае это только [11 141] совместимый с пустотой блок .

Вспоминание, что мы отметили ранее, это означает, что в первом случае, лямбда будет потенциально совместима и с [1 137] и Runnable; во втором случае лямбда только будет потенциально совместима с [1 139].

Выбирают Most Specific Method

For первый случай, компилятор должен выбрать между двумя методами, потому что оба потенциально применимы . Это делает настолько использующий процедуру, названную, 'Выбирают Самый Определенный Метод' и описал в 15.12.2.5$. Вот выборка:

А функциональный интерфейсный тип S более специфичен, чем функциональный интерфейсный тип T для выражения e, если T не является подтипом S, и одно из следующего верно (где U1... Великобритания и R1 являются типами параметра и типом возврата функционального типа получения S и V1... Vk и R2 являются типами параметра и типом возврата функционального типа T):

, Если e является явно введенным лямбда-выражением (В§15.27.1), то одно из следующего верно:

R2 является пустым.

, В первую очередь,

лямбда-выражение А с нулевыми параметрами явно вводится.

кроме того, ни один из [1 140] и Callable не является подклассом друг друга, и Runnable, тип возврата void, таким образом, у нас есть соответствие: Callable более конкретно, чем [1 145] . Это означает, что между [1 146] и submit(Runnable) в первом случае метод с [1 148] будет выбран.

Что касается второго случая, там у нас только есть один потенциально применимый метод, submit(Runnable), таким образом, он выбран.

Итак, почему изменение появляется?

Так, в конце, мы видим, что в этих случаях различные методы выбраны компилятором. В первом случае лямбда выведена, чтобы быть Callable, который имеет throws Exception на call() метод, так, чтобы sleep() вызов скомпилировал. Во втором случае это Runnable, который run() не объявляет throwable исключений, таким образом, компилятор жалуется на исключение, не будучи пойманным.

3
ответ дан 31 October 2019 в 14:29

Другие вопросы по тегам:

Похожие вопросы: