如果我尝试编译
for(;;)
{
}
System.out.println("End");
Java 编译器产生一个错误,说Unreachable statement
. 但是,如果我添加另一个“无法访问”(根据我)break
声明并使它:
for(;;)
{
if(false) break;
}
System.out.println("End");
它编译。为什么它不会产生错误?
如果我尝试编译
for(;;)
{
}
System.out.println("End");
Java 编译器产生一个错误,说Unreachable statement
. 但是,如果我添加另一个“无法访问”(根据我)break
声明并使它:
for(;;)
{
if(false) break;
}
System.out.println("End");
它编译。为什么它不会产生错误?
该行为在无法访问语句的 JLS 描述中定义:
如果 if-then 语句可达,则 then 语句可达。
因此编译器确定 then 语句 ( break;
) 是可达的,而不管if
.
再进一步,强调我的:
for
如果以下至少一项为真,则 基本语句可以正常完成:
- for 语句可达,有条件表达式,且条件表达式不是值为 true 的常量表达式(第 15.28 节)。
- 有一个可到达的
break
语句退出 for 语句。
所以 for 可以正常完成,因为 then 语句包含一个break
. 正如您所注意到的,如果您将其替换break
为return
.
基本原理将在本节末尾进行解释。实质上,if
具有特殊处理以允许构造,例如:
if(DEBUG) { ... }
其中 DEBUG 可能是编译时间常数。
正如我在对类似问题的回答中所解释的那样,特定构造if(compile-time-false)
作为显式后门不受不可达性规则的约束。在这种情况下,编译器因此将您break
视为可访问的。
来自JLS
如果以下至少一项为真,则 if-then 语句可以正常完成:
> if-then 语句可达且条件表达式不是值为真的常量表达式。
> then-语句可以正常完成。
所以if(false)
是允许的。
这种“有条件地编译”的能力对二进制兼容性有很大的影响和关系。如果编译了一组使用这种“标志”变量的类并且省略了条件代码,那么稍后仅分发包含标志定义的类或接口的新版本是不够的。因此,对标志值的更改与预先存在的二进制文件不兼容。(这种不兼容还有其他原因,例如在 switch 语句中的 case 标签中使用常量;)
基本上,通过静态分析程序而不实际运行代码来检测无法访问的代码。而条件将在运行时检查。因此,当进行此分析时,它实际上并没有查看条件,而只是检查是否可以通过 访问(可达)。 break;
if
Java 没有检测到所有不可达语句的核心原因是通常无法回答代码是否可达。这是因为停机问题在图灵机上是不可判定的。
所以,很明显,所有无法到达的语句都无法检测到,但为什么不尝试评估条件呢?false
现在想象一下,所使用的条件不仅仅是~x == x
. 例如,所有这些语句都true
将为每个int x
( source ) 打印。
System.out.println((x + x & 1) == 0);
System.out.println((x + -x & 1) == 0);
System.out.println((-x & 1) == (x & 1));
System.out.println(((-x ^ x) & 1) == 0);
System.out.println((x * 0x80 & 0x56) == 0);
System.out.println((x << 1 ^ 0x1765) != 0);
陈述可能相当复杂;解决它们需要时间。它会显着增加构建时间,毕竟它不会检测到所有无法访问的语句。编译器旨在采取一些努力,但不会为此花费太多时间。
剩下的唯一问题是:在哪里停止解决条件?其原因似乎没有数学依据,而是基于使用场景。JLS-14.21给出了您的特定案例的基本原理