我的问题是关于InterruptedException
的,它是从Thread.sleep
方法抛出的,在使用ExecutorService
时,我注意到一些奇怪的行为,我不明白;我意思是:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while(true)
{
//DO SOMETHING
Thread.sleep(5000);
}
});
在这段代码中,编译器不会给出任何错误信息,也不会告诉我应该捕捉来自Thread.sleep
的InterruptedException
,但是当我试图改变循环条件,用下面这样的变量替换“true”时:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while(tasksObserving)
{
//DO SOMETHING
Thread.sleep(5000);
}
});
编译器不断地抱怨必须处理InterruptedException
。有人能给我解释一下为什么会发生这种情况吗?为什么如果条件设置为true,编译器会忽略InterruptedException?
2条答案
按热度按时间u3r8eeie1#
这样做的原因是,这些调用实际上是对
ExecutorService
中可用的两个不同重载方法的调用;这些方法中的每一个都接受不同类型的单个参数:<T> Future<T> submit(Callable<T> task);
2.Future<?> submit(Runnable task);
然后,编译器将第一个问题中的lambda转换为
Callable<?>
函数接口(调用第一个重载的方法);在第二种情况下,将lambda转换为Runnable
函数接口(因此调用第二个重载方法),因此需要处理抛出的Exception
;但在先前使用X1 M6 N1 X的情况下不是这样。尽管两个函数接口都不接受任何参数,但
Callable<?>
返回一个值:1.可调用〈?〉:
V call() throws Exception;
2.可运行:public abstract void run();
如果我们切换到将代码裁剪为相关片段的示例(以便轻松地研究那些奇怪的部分),那么我们可以编写与原始示例等价的代码:
通过这些示例,可以更容易地观察到第一个被转换为
Callable<?>
而第二个被转换为Runnable
的原因是由于 * 编译器推断 *。在这两种情况下,lambda主体都是void兼容的,因为块中的每个return语句都具有
return;
的形式。现在,在第一种情况下,编译器执行以下操作:
1.检测lambda中的所有执行路径都声明引发checked exceptions(从现在起,我们将称为 “异常”,仅表示 “已检查异常”)。这包括调用任何声明引发异常的方法以及显式调用
throw new <CHECKED_EXCEPTION>()
。1.正确地得出结论,lambda的WHOLE主体等效于声明引发异常的代码块;当然,必须为:处理或重新投掷。
1.因为lambda不处理异常,那么编译器默认假设这些异常必须被重新抛出。
1.安全地推断此lambda必须匹配函数接口,而函数接口不能
complete normally
,因此是值兼容的。1.由于
Callable<?>
和Runnable
是这个lambda的潜在匹配,编译器选择最具体的匹配(以覆盖所有场景);即Callable<?>
,将lambda转换为它的示例,并创建对submit(Callable<?>)
重载方法的调用引用。而在第二种情况下,编译器执行以下操作:
1.检测lambda中可能存在DO NOT声明引发异常的执行路径(取决于 * 要计算的逻辑 *)。
1.由于并非所有执行路径都声明抛出异常,编译器得出结论,lambda的主体不必等同于声明抛出异常的代码块-编译器不关心/注意代码的某些部分是否声明它们可能,只有整个主体是否声明。
1.安全地推断lambda与值不兼容;因为它是五月
complete normally
。1.选择
Runnable
(因为它是lambda要转换到的唯一可用的 fitting 函数接口)并创建对submit(Runnable)
重载方法的调用引用。所有这些都是以委托给用户为代价的,委托用户处理任何抛出的Exception
,无论它们可能出现在lambda主体的任何部分。z0qdvdin2#
简单来说
ExecutorService
同时具有submit(Callable)
和submit(Runnable)
方法。while (true)
),submit(Callable)
和submit(Runnable)
都匹配,因此编译器必须在它们之间进行选择submit(Callable)
而不是submit(Runnable)
是因为Callable
比Runnable
* 更具体Callable
在call()
中具有throws Exception
,因此没有必要捕获其中的异常while (tasksObserving)
),只有submit(Runnable)
匹配,因此编译器选择它Runnable
在其run()
方法上没有throws
声明,因此在run()
方法内未捕获异常是一个编译错误。∮完整的故事∮
Java语言规范在$15.2.2中描述了如何在程序编译期间选择方法:
1.识别可能适用的方法($15.12.2.1),分为3个阶段,分别用于严格、宽松和可变arity调用
1.从第一步找到的方法中选择最具体的方法(15.12.2.5美元)。
让我们在OP提供的两个代码片段中分析2个
submit()
方法的情况:以及
(其中
tasksObserving
不是最终变量)。确定潜在适用方法
首先,编译器必须识别 * 可能适用的方法 *:十五元一二角二分一
如果成员是一个固定arity方法,arity为n,则方法调用的arity等于n,并且对于所有i(1 ≤ i ≤ n),方法调用的第i个参数与方法的第i个参数的类型 * 潜在兼容 *,如下所定义。
在同一部分再往前一点
根据以下规则,表达式与目标类型 * 可能兼容 *:
如果满足以下所有条件,则lambda表达式(参见15.27节)可能与函数接口类型(参见9.8节)兼容:
目标类型的函数类型的arity与lambda表达式的arity相同。
如果目标类型的函数类型有一个void返回,那么lambda主体要么是一个语句表达式(参见14.8节),要么是一个void兼容块(参见15.27.2节)。
如果目标类型的函数类型有一个(非空的)返回类型,那么lambda主体要么是一个表达式,要么是一个值兼容的块(参见15.27.2节)。
让我们注意在这两种情况下,lambda都是块lambda。
我们还要注意
Runnable
有void
返回类型,所以要与Runnable
* 潜在兼容 *,块lambda必须是 * void兼容块 *。同时,Callable
有非void返回类型,所以要与Callable
* 潜在兼容 *,块lambda必须是 * value兼容块 *。$15.27.2定义了什么是 * 空兼容块 * 和 * 值兼容块 *。
如果块中的每个return语句都具有
return;
,则块lambda主体是void兼容的。块lambda体是值兼容的,如果它不能正常完成(参见14.21节),并且块中的每个return语句的形式都是
return Expression;
。让我们看一下$14.21,关于
while
循环的段落:while语句可以正常完成的条件是至少满足以下条件之一:
while语句是可达的,条件表达式不是值为true的常量表达式(参见15.28节)。
存在退出while语句的可访问break语句。
在borh的情况下,lambda实际上是块lambda。
在第一种情况下,如图所示,存在一个
while
循环,其中常量表达式的值为true
(没有break
语句),因此它无法正常完成(相差$14.21);而且,它没有返回语句,因此第一个lambda是 * 值兼容的 *。同时,根本没有
return
语句,所以它也是 * void兼容的 *,所以,最后,在第一种情况下,lambda是void和value兼容的。在第二种情况下,从编译器的Angular 来看,
while
循环 * 可以正常地完成 *(因为循环表达式不再是常量表达式),所以整个lambda * 可以正常地完成 *,所以它不是一个 * 值兼容的块 *,但是它仍然是一个void兼容的块,因为它不包含return
语句。中间结果是,在第一种情况下,lambda既是 * 空兼容块 * 又是 * 值兼容块 *;在第二种情况下,它是唯一的空兼容块 *。
回想一下我们前面提到的,这意味着在第一种情况下,lambda将与
Callable
和Runnable
都是 * 潜在兼容的 *;在第二种情况下,λ将仅与X1 M37 N1 X“潜在兼容”。选择最具体的方法
对于第一种情况,编译器必须在这两种方法之间做出选择,因为它们都 * 可能适用 *。它使用$15.12.2.5中描述的名为'Choose the Most Specific Method'的过程来完成。
对于表达式e,如果T不是S的子类型并且以下之一为真,则函数接口类型S比函数接口类型T更具体(其中U1...Uk和R1是S的捕获的函数类型的参数类型和返回类型,并且V1...Vk和R2是T的函数类型的参数类型和返回类型):
如果e是一个显式类型的lambda表达式(参见15.27.1节),那么以下条件之一成立:
R2无效。
首先,
具有零个参数的lambda表达式是显式类型的。
另外,
Runnable
和Callable
都不是彼此的子类,并且Runnable
返回类型是void
,所以我们有一个匹配:* *Callable
比Runnable
更具体**。这意味着在第一种情况下,在submit(Callable)
和submit(Runnable)
之间将选择使用Callable
的方法。对于第二种情况,我们只有一个 * 潜在适用的 * 方法
submit(Runnable)
,因此选择它。那么,为什么表面会发生变化呢?
最后,我们可以看到,在这些情况下,编译器选择了不同的方法,在第一种情况下,lambda被推断为
Callable
,它的call()
方法上有throws Exception
,所以sleep()
调用编译,在第二种情况下,它是Runnable
,run()
没有声明任何可抛出的异常,因此编译器会抱怨没有捕获到异常。