我在java中做了一些小程序。我知道如果我写 while(true);
,程序将在此循环中冻结。如果代码是这样的:
public class While {
public static void main(String[] args) {
System.out.println("start");
while (true);
System.out.println("end");
}
}
编译器抛出错误:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Unreachable code
at While.main(While.java:6)
我不知道这个错误存在。但我知道它被抛出的原因。当然,第6行无法访问,导致编译问题。然后我测试了这个:
public class While {
public static void main(String[] args) {
System.out.println("start");
a();
b();
}
static void a() {
while(true);
}
static void b() {
System.out.println("end");
}
}
出于某种原因程序正常运行(控制台打印“开始”然后冻结)。编译器无法检查 void a()
内部,看到它无法访问。确定我试过了:
public class While {
public static void main(String[] args) {
System.out.println("start");
a();
System.out.println("end");
}
static void a() {
while(true);
}
}
与测试2相同的结果。
经过一些研究后,我发现了 question 。 因此,如果括号内的代码是变量,则编译器不会抛出异常。这是有道理的,但我不认为这同样适用于 voids
。
问:那么,如果 void b()
(测试2)和 {{,为什么编译器只会在测试1中抛出错误1}} (测试3)无法访问?
编辑:我在C ++中尝试过测试1:
System.out.println("end");
编译器没有抛出任何错误,然后我得到了与测试2和测试3相同的结果。所以我想这是一个java的东西?
答案 0 :(得分:20)
language spec has an exact definition编译器应将其视为无法访问的代码,另请参阅https://stackoverflow.com/a/20922409/14955。
特别是,它不关心方法是否完成,并且它不会查看其他方法。
它不会做更多的事情。
但是,您可以使用像FindBugs这样的静态代码分析工具来获得更深层次的"分析(不确定他们是否检测到你描述的模式,或者,正如其他人所指出的那样,所有普遍性中的暂停问题无论如何都无法通过算法解决,因此必须在某种合理的定义中划清界限。 "尽力而为#34;)。
答案 1 :(得分:12)
一般情况下,无法确定是否可以达到某些内容。
为什么呢?它相当于Halting Problem。
暂停问题:
给出任意计算机程序的描述,确定程序是否完成运行或继续运行。
这个问题已被证明无法解决。
X代码是否可访问与说明之前的代码是否会停止相同。
因为它是一个无法解决的问题,编译器(用Java或任何其他语言)不会非常努力地解决它。如果碰巧确定它确实无法访问,那么您会收到警告。如果没有,它可能会或可能不会到达。
在Java中,无法访问的代码是编译器错误。因此,为了保持兼容性,语言规范准确地定义了编译器应该尝试的“有多难”。 (根据其他答案,“不要进入另一个功能”。)
在其他语言(例如C ++)中,编译器可能会进一步优化。 (在内联函数并发现它永远不会返回之后,可能会检测到无法访问的代码。)
答案 2 :(得分:12)
Unreachable code是一个编译时错误,只是说'这个程序的流程没有意义;永远不会达到某些东西' 。
显然,由于无限循环,您的测试会执行它们的工作方式,但为什么第一个会因编译时错误而失败?
如果至少有一个,则while语句可以正常完成 以下是真的:
可以访问while语句,条件表达式不是a 常量表达式(§15.28),值为true。
有一个可到达的break语句退出while语句。
好的,但是方法调用(例如a()
) - 为什么测试2和3成功编译?
- 如果表达式语句可以访问,则表达式语句可以正常完成。
由于方法调用被视为表达式,因此只要在阻塞逻辑执行路径之前没有任何内容,它们将始终可以访问。
为了更好地说明这种编译机制背后的一些推理,让我们举一个if
语句,例如。
if(false)
System.out.println("Hello!"); // Never executes
以上在编译时是正确的(虽然很多IDE肯定会抱怨!)。
The Java 1.7 Specification talks about this:
这种不同处理的理由是允许程序员 定义"标志变量"如:
static final boolean DEBUG = false;
然后编写如下代码:
if (DEBUG) { x=3; }
这个想法是应该有可能改变 DEBUG的值从false到true或从true到false然后 正确编译代码而不对程序文本进行其他更改。
此外,实际上也是向后兼容的原因:
这种能够有条件地编译"对...有重大影响, 和二进制兼容性(§13)的关系。如果是一组课程 使用这样一个"标志"变量是编译的,条件代码是 省略,以后只分发一个新版本是不够的 包含标志定义的类或接口。一个 因此,更改为标志的值不是二进制兼容的 使用预先存在的二进制文件(§13.4.9)。 (还有其他原因 这种不兼容性,例如在情况下使用常数 switch语句中的标签;见§13.4.9。)
大多数(根据规范),如果不是全部,Java编译器的实现不遍历方法。在解析Java代码本身时,它会将a()
视为MethodInvocationElement
,这意味着'此代码调用其他代码。我真的不在乎,我只是在看语法。' 。 语法 ,在调用a()
之后,后续代码的归属是有意义的。
请记住性能成本。编译已经花费了相当多的时间。为了保持快速,Java编译器实际上没有进入方法;这需要很长时间(编译器必须评估许多很多代码路径 - 理论上)。
进一步重申它的语法驱动是在return;
循环之后直接添加a()
语句。没有编译,是吗? 语法,但没有它就没有意义。
答案 3 :(得分:6)
答案在于Java Language Specification为可达性制定的规则。它首先说明
如果语句因无法访问而无法执行,则为编译时错误。
然后
while
语句可以正常完成iff至少其中一个 以下是真的:
while
语句是可以访问的,条件表达式不是值为true
的常量表达式(第15.28节)。- 有一个可访问的
break
语句退出while语句。
和
如果表达式语句可以访问,则表达式语句可以正常完成。
在第一个示例中,您有一个无法正常完成的while循环,因为它的条件是值为true
的常量表达式,并且其中没有可访问的break
。
在第二个和第三个示例中,expression statement(方法调用)可以访问,因此可以正常完成。
所以我想这是一个java的东西?
上述规则是Java的规则。与其他语言一样,C ++可能有自己的规则。