这样做的原因是,这些调用实际上是对
ExecutorService;中可用的两种不同的重载方法的调用。这些方法中的每一个均采用不同类型的单个参数:
<T> Future<T> submit(Callable<T> task); Future<?> submit(Runnable task);
然后发生的事情是,编译器正在将问题的第一种情况下的lambda转换为
Callable<?>功能接口(调用第一个重载方法)。在第二个问题中,您将lambda转换为
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(); });
通过这些示例,可能更容易观察到第一个被转换为a
Callable<?>,而第二个被转换为a
Runnable的原因是由于 编译器推断 。
在这两种情况下,lambda主体都是无效的,因为该块中的每个return语句都具有形式
return;。
现在,在第一种情况下,编译器将执行以下 *** 作:
- 检测到lambda中的所有执行路径都声明抛出检查过的异常(从现在开始,我们将其称为 “ exception” ,仅表示 “ checked exceptions” )。这包括调用任何声明抛出异常的方法以及对的显式调用
throw new <CHECKED_EXCEPTION>()
。 - 正确的结论是, WHOLE 拉姆达的身体相当于代码声明抛出异常块; 当然 必须 :处理或重新抛出。
- 由于lambda不会处理该异常,因此编译器默认情况下假定必须重新抛出这些异常。
- 安全地推断此lambda必须与功能接口匹配
complete normally
,因此是值兼容的。 - 由于
Callable<?>
和Runnable
是该lambda的潜在匹配项,因此编译器会选择最具体的匹配项(以涵盖所有方案);这是Callable<?>
,将lambda转换为其实例,并创建对submit(Callable<?>)
重载方法的调用引用。
在第二种情况下,编译器执行以下 *** 作:
- 检测lambda中可能存在 不 声明抛出异常的执行路径(取决于 要评估的逻辑 )。
- 由于不是所有的执行路径声明抛出异常,编译器的结论是,拉姆达的身体是 不是必然 等同于代码声明抛出异常块-编译不在乎/注意,如果代码的某些部分做宣布,他们可能,只有整个身体都可以。
- 安全地推断出lambda不兼容价值;自 五月 以来
complete normally
。 - 选择
Runnable
(因为它是要转换为lambda 的唯一可用的 拟合 功能接口),并创建对submit(Runnable)
重载方法的调用引用。所有这些都是以委托给用户为代价的,即有责任处理 可能* 在lambda主体部分中发生的任何Exception
地方所引发的所有问题。 *
这是一个很好的问题-追逐它让我很开心,谢谢!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)