为什么Java有一个“无法访问的语句”编译器错误?

我经常在debugging程序时发现它很方便(虽然可以说是不好的做法)在代码块中插入return语句。 我可能会尝试在Java中这样的东西….

class Test { public static void main(String args[]) { System.out.println("hello world"); return; System.out.println("i think this line might cause a problem"); } } 

当然,这会产生编译器错误。

Test.java:7:无法访问的语句

我可以理解为什么警告可能是合理的,因为没有使用的代码是不好的做法。 但我不明白为什么这需要产生一个错误。

这只是Java试图成为一个保姆,还是有一个很好的理由,使这个编译器错误?

因为不可达的代码对编译器没有意义。 编译器对代码有意义的代码既重要又困难,而编译器是代码的基本使用者。 Java的devise者们认为编译器没有意义的代码是一个错误。 他们的立场是,如果你有一些不可达的代码,你犯了一个错误,需要修复。

这里有一个类似的问题: 无法访问的代码:错误或警告? 其中作者说:“就我个人而言,我强烈地感到这应该是一个错误:如果程序员写了一段代码,应该总是有意在某些情况下实际运行它。 Java的语言devise者显然同意。

不可达代码是否应该防止编译是一个永远不会达成共识的问题。 但是这就是Javadevise师为什么这么做的原因。


许多人在评论中指出,有许多类不可达代码Java不会阻止编译。 如果我正确理解了Gödel的后果,那么编译器就不可能捕获到所有不可达代码的类。

unit testing不能捕捉到每一个错误。 我们不把这作为反对他们的价值的论据。 同样,一个编译器不能捕获所有有问题的代码,但是它仍然有价值,因为它可以防止编译错误的代码。

Java语言devise者认为无法访问的代码是一个错误。 所以尽可能地防止编译是合理的。


(之前,你的问题并不是Java是否应该有一个不可达的语句编译器错误,问题是为什么 Java有一个不可达的语句编译器错误,不要因为你认为Java做出了错误的devise决定而低估了我)。

没有明确的理由说明为什么不能得到不可达的陈述; 其他语言让他们没有问题。 为了您的具体需要,这是通常的把戏:

 if (true) return; 

它看起来没有任何意义,任何阅读代码的人都会猜测,它一定是故意的,而不是一个粗心的错误,让其余的语句不可及。

Java对“条件编译”有一点支持,

http://java.sun.com/docs/books/jls/third_edition/html/statements.html#14.21

 if (false) { x=3; } 

不会导致编译时错误。 一个优化编译器可能会意识到这个语句x = 3; 将永远不会执行,并且可能会从生成的类文件中select省略该语句的代码,但是语句x = 3; 在这里指定的技术意义上不被认为是“无法达到的”。

这种不同的处理方式的基本原理是允许程序员定义“标志variables”,例如:

 static final boolean DEBUG = false; 

然后编写代码如:

 if (DEBUG) { x=3; } 

这个想法是应该可以将DEBUG的值从false更改为true,或者从true更改为false,然后正确编译代码,而不会对程序文本进行其他更改。

这是保姆。 我感觉.net得到了这个正确的 – 它提出了一个警告,无法访问的代码,但不是一个错误。 这是很好的警告,但我没有理由阻止编译(特别是在debugging会议,很好地抛出一个绕过一些代码的回报)。

我只是注意到了这个问题,并且想把我的$ .02添加到这个。

在Java的情况下,这实际上不是一个选项。 “无法访问的代码”错误并不是来自于JVM开发人员认为要保护开发人员不受任何事情影响的事实,也不是来自JVM规范要求的额外警惕。

Java编译器和JVM都使用所谓的“堆栈映射”,即为当前方法分配的有关堆栈中所有项目的明确信息。 必须知道堆栈中每个插槽的types,以便JVM指令不会将另一种types的项目作为干扰项目。 这对于防止将数值用作指针来说是非常重要的。 使用Java程序集可以尝试推送/存储一个数字,然后popup/载入一个对象引用。 但是,在类validation过程中,JVM将拒绝这个代码,即在创build堆栈映射并testing其一致性时。

为了validation堆栈映射,虚拟机必须遍历方法中存在的所有代码path,并确保无论执行哪条代码path,每条指令的堆栈数据都与之前的代码所推送的内容一致/存储在堆栈中。 所以,在简单的情况下:

 Object a; if (something) { a = new Object(); } else { a = new String(); } System.out.println(a); 

在第3行,JVM会检查两个分支是否只存储到一个(这只是局部variables#0)的东西是兼容的对象(因为这是如何代码从第3行,将对待本地var#0 )。

当编译器得到一个不可达的代码时,它并不完全知道堆栈可能处于什么状态,所以无法validation它的状态。 由于它不能跟踪局部variables,所以在这一点上不能完全编译代码,所以不会在类文件中留下这种不明确的地方,而是会产生一个致命的错误。

当然,像if (1<2)这样的简单条件会欺骗它,但这并不是真的愚蠢 – 它给了它一个可能导致代码的潜在分支,至less编译器和虚拟机都可以确定堆栈物品可以从那里使用。

PS我不知道.NET在这种情况下做了什么,但是我相信它也会编译失败。 对于任何机器代码编译器(C,C ++,Obj-C等)来说,这通常不成问题。

编译器的目标之一是排除错误的类别。 有些无法访问的代码是偶然发生的,在编译时javac排除这类错误是很好的。

对于捕获错误代码的每条规则,有人会希望编译器接受它,因为他们知道自己在做什么。 这是编译器检查的惩罚,正确的平衡是语言devise的一个难点。 即使经过最严格的检查,仍然可以编写无数的程序,所以事情不会那么糟糕。

虽然我认为这个编译器错误是一件好事,但有一种方法可以解决它。 使用你知道的条件是正确的:

 public void myMethod(){ someCodeHere(); if(1 < 2) return; // compiler isn't smart enough to complain about this moreCodeHere(); } 

编译器不够聪明来抱怨。

抱怨编译器越严格越好,只要它允许你做你所需要的就是一件好事。 通常要付出的小代价是将代码注释掉,获得的好处就是当你编译代码的时候工作。 Haskell就是一个通用的例子,他们直到他们意识到他们的testing/debugging只是主要的testing和短的testing。 我个人在Java中几乎没有debugging(其实是故意的)不注意。

如果允许if (aBooleanVariable) return; someMoreCode;的原因if (aBooleanVariable) return; someMoreCode; if (aBooleanVariable) return; someMoreCode; 是允许标志,那么事实, if (true) return; someMoreCode; if (true) return; someMoreCode; 不生成编译时错误,似乎在生成CodeNotReachableexception的策略中不一致,因为编译器“知道” true不是标志(不是variables)。

其他两种可能有趣的方式,但不适用于closures部分方法的代码以及if (true) return

现在,而不是说if (true) return; 您可能想要assert false ,并将-ea OR -ea package OR -ea className到jvm参数中。 好处是这允许一些粒度,并且需要为jvm调用添加额外的参数,所以不需要在代码中设置DEBUG标志,而是在运行时添加参数,当目标不是开发者机器和重新编译和传输字节码需要时间。

还有System.exit(0)方法,但是这可能是一个矫枉过正的问题,如果你把它放在Java中的JSP中,那么它会终止服务器。

除了Java被devise成“保姆”语言之外,我宁愿使用像C / C ++这样的本机来进行更多的控制。

Interesting Posts