Причина этого, то, что эти вызовы на самом деле, вызовы к двум различным перегруженным методам, доступным в ExecutorService
; каждый из этих методов, берущих отдельный аргумент различных типов:
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
Затем то, что происходит, - то, что компилятор преобразовывает лямбду в первом случае Вашей проблемы в Callable<?>
функциональный интерфейс (вызов первого перегруженного метода); и во втором случае Вашей проблемы преобразовывает лямбду в Runnable
функциональный интерфейс (вызывающий поэтому второй перегруженный метод), требуя из-за этого для обработки Exception
брошенный; но не в предыдущем случае с помощью Callable
.
, Хотя оба функциональных интерфейса не берут аргументов, Callable<?>
, возвращает значение :
- Вызываемый:
V call() throws Exception;
- Выполнимый:
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;
.
Теперь, в первом случае, компилятор делает следующее:
complete normally
и поэтому совместимы со значением . Callable<?>
и Runnable
потенциальные соответствия для этой лямбды, компилятор выбирает самое определенное соответствие (для покрытия всех сценариев); который является эти Callable<?>
, преобразовывая лямбду в экземпляр его и создавая ссылку вызова на submit(Callable<?>)
перегруженный метод. , В то время как во втором случае компилятор делает следующее:
complete normally
. Runnable
(поскольку это - единственное доступное установка функциональный интерфейс для лямбды, которая будет преобразована в), и создает ссылку вызова на submit(Runnable)
перегруженный метод. Все это прибытие в цену делегирования пользователю, ответственности обработки любого Exception
с, брошенная везде, где они МОГУТ , происходит в частях тела лямбды. Это было большим вопросом - я хорошо провел время, упорно ища его, Спасибо!
ExecutorService
имеет и submit(Callable)
и submit(Runnable)
методы.
while (true)
), и submit(Callable)
и submit(Runnable)
соответствие, таким образом, компилятор должен выбрать между ними submit(Callable)
, предпочтено submit(Runnable)
, потому что Callable
более конкретны , чем [1 111] Callable
, имеет throws Exception
в [1 114], таким образом, не необходимо поймать исключение в нем while (tasksObserving)
) только [1 116] соответствие, таким образом, компилятор выбирает, это Runnable
не имеет никакого throws
объявление по run()
метод, таким образом, это - ошибка компиляции для не ловли исключения в run()
метод. Спецификация языка Java описывает, как метод выбран во время компиляции программы в [1 156] 15.2.2$ :
Позволяют нам проанализировать ситуацию с 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].
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 исключений, таким образом, компилятор жалуется на исключение, не будучи пойманным.