Java为什么会有“无法到达的语句”编译器错误?


83

我经常在调试程序时发现在代码块内插入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试图成为一个保姆,还是有充分的理由使它成为编译器错误?


11
Java也不完全一致。对于某些导致死代码的控制流,但是Java不会抱怨。对于其他人则如此。确定什么代码已失效是无法解决的问题。我不知道为什么Java决定启动无法完成的事情。
Paul Draper

Answers:


63

因为无法到达的代码对编译器毫无意义。尽管使代码对人有意义不仅比使代码对编译器有意义更困难,但编译器是代码的基本使用者。Java设计人员认为对编译器没有意义的代码是错误。他们的立场是,如果您有一些无法访问的代码,那么您犯了一个错误,需要修复。

这里有一个类似的问题:无法访问的代码:错误还是警告?,其中作者说:“我个人强烈认为这应该是一个错误:如果程序员编写一段代码,则应该始终在某种情况下实际运行它。” 显然,Java的语言设计师对此表示同意。

无法访问的代码是否应该阻止编译是一个永远不会达成共识的问题。但这就是Java设计人员这样做的原因。


许多评论的人指出,Java无法阻止编译的许多类无法访问的代码。如果我正确理解了Gödel的后果,那么没有编译器可以捕获所有无法访问的代码类。

单元测试无法捕获每个错误。我们不以此为理由反对它们的价值。同样,编译器无法捕获所有有问题的代码,但对于它可以防止编译不良代码仍然很有价值。

Java语言设计人员将无法访问的代码视为错误。因此,在可能的情况下防止其编译是合理的。


(在降低表决权之前:问题不是Java是否应具有不可到达的语句编译器错误。问题是Java为什么会有不可达到的语句编译器错误。请不要仅仅因为您认为Java做出了错误的设计决定就对我进行表决。)


22
显然,Java的语言设计师同意Java的语言设计师”是人类,并且也容易出错。就我个人而言,我认为您提到的讨论的另一面更有力。
Nikita Rybak 2010年

6
@Gabe:因为它不是无害的-几乎可以肯定这是一个错误。您将代码放置在错误的位置,或者误解了您编写的语句无法到达其中的某种方式。使此错误成为错误可防止编写错误的代码,并且如果您希望代码中无法访问的内容供其他开发人员阅读(无法访问的代码的唯一受众),请改用注释。
SamStephens

10
SamStephens:视为未调用的方法和未使用的变量实质上是所有形式的不可访问代码。为什么允许某些形式而不允许其他形式?特别是为什么不允许这种有用的调试机制?
加布

5
难道评论也无法达成吗?在我看来,无法访问的代码实际上是一种注释形式(这是我之前尝试做的事情,等等)。但是考虑到“真实的”评论也不是“可访问的”……也许他们也应该提出这个错误;)
布雷迪·莫里茨

10
问题是他们(Java语言设计者)与此不一致。如果有实际的流量分析,则很难意识到两者return; System.out.println();if(true){ return; } System.out.println();保证具有相同的行为,但是一个是编译错误,而另一个则不是。因此,在调试时,我使用此方法临时“注释掉”了代码,这就是我的方法。注释掉代码的问题是,您必须找到适当的位置来停止注释,这可能比return;真正投入时间要长。
LadyCailin 2012年

47

没有绝对的理由为什么不允许不可达的语句;其他语言允许他们没有问题。对于您的特定需求,这是通常的技巧:

if (true) return;

看起来很荒谬,任何阅读该代码的人都会猜测它一定是有意完成的,而不是一个使其余语句无法访问的粗心大意的错误。

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

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

if (false) { x=3; }

不会导致编译时错误。优化的编译器可能会意识到语句x = 3; 永远不会执行,可以选择从生成的类文件中省略该语句的代码,但是语句x = 3; 就此处指定的技术意义而言,“不被视为“无法访问”。

这种不同处理的原理是允许程序员定义“标志变量”,例如:

static final boolean DEBUG = false;

然后编写如下代码:

if (DEBUG) { x=3; }

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


2
仍然不知道为什么要打扰您所展示的技巧。无法访问的代码对编译器毫无意义。因此,它的唯一受众是开发人员。使用评论。尽管我猜想是否要暂时添加收益,但是使用变通办法可能比仅输入收益并注释掉以下代码要快一些。
SamStephens

8
@SamStephens但是,每次您要切换时,都必须记住所有必须注释掉代码的地方以及必须做相反的事情。您将不得不通读整个文件,更改源代码,可能时不时地犯一些细微的错误,而不仅仅是在一个地方用“ false”替换“ true”。我认为最好对开关逻辑进行一次编码,除非必要,否则不要触摸它。
罗伯特

“任何阅读该代码的人都会猜到它一定是故意做的”。我认为这正是问题所在,人们不应该猜测,他们应该理解。记住代码是与人交流,而不仅仅是计算机指令。如果除了临时性以外,我认为应该使用配置。查看上面显示的内容,if (true) return;并不表示您为什么要跳过逻辑,而是在if (BEHAVIOURDISABLED) return;传达意图。
SamStephens 2012年

5
这个技巧对于调试确实很有用。我经常添加一个if(true)返回值,以消除试图找出失败原因的方法的“其余部分”。还有其他的方法,但这是干净和快速的-但它不是你应该没有检查的东西。
比尔ķ

18

是保姆。我觉得.Net正确地做到了这一点-它对无法访问的代码提出警告,但不是错误。对此有个警告,但我认为没有理由阻止编译(尤其是在调试会话期间,最好返回一个返回值以绕过某些代码)。


1
Java的设计方法较早,当时每个字节都很重要,软盘仍然是高科技。
无可争议的

1
对于调试早期返回,在早期返回之后,还需要五秒钟来注释掉各行。通过使无法访问的代码成为错误来防止这些错误,为此付出的代价很小。
SamStephens

6
编译器很容易排除无法访问的代码。没有理由强迫开发商这样做。
布雷迪·莫里茨

2
@SamStephens如果您已经在这里没有注释,则不会,因为注释不会堆叠,因此不必对注释进行处理。
enrey

@SamStephens注释掉的代码不能很好地重构。
cowlinator

14

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

对于Java,这实际上不是一个选择。“无法到达的代码”错误并非来自JVM开发人员认为保护开发人员免受任何攻击或保持警惕的事实,而是来自JVM规范的要求。

Java编译器和JVM都使用所谓的“堆栈映射”-有关分配给当前方法的堆栈上所有项目的明确信息。必须知道堆栈中每个插槽的类型,以便JVM指令不会将一种类型的项目误认为另一种类型。这对于防止将数值用作指针非常重要。使用Java程序集可以尝试推送/存储数字,然后弹出/加载对象引用。但是,JVM将在类验证期间(即正在创建堆栈映射并测试一致性时)拒绝此代码。

为了验证堆栈映射,VM必须遍历方法中存在的所有代码路径,并确保无论执行哪个代码路径,每条指令的堆栈数据都应与之前的任何代码一致/存储在堆栈中。因此,在以下简单情况下:

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

在第3行,JVM将检查'if'的两个分支是否仅存储到与Object兼容的东西(这只是本地var#0)中(因为这是第3行及以后的代码将如何处理本地var#0 )。

当编译器获取到无法访问的代码时,它并不十分了解此时堆栈可能处于什么状态,因此无法验证其状态。此时它无法完全编译代码,因为它也无法跟踪局部变量,因此,它不会在类文件中留下这种歧义,而是产生致命错误。

当然,像这样的简单条件if (1<2)会使它傻瓜,但这并不是真正的愚弄-它为它提供了一个可能导致代码生成的潜在分支,并且至少编译器和VM都可以确定如何从那里使用堆栈项上。

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


Java的死代码警告系统有多积极?可以将其表示为语法的一部分(例如{ [flowingstatement;]* }=> flowingstatement{ [flowingstatement;]* stoppingstatement; }=> stoppingstatementreturn=> stoppingstatementif (condition) stoppingstatement; else stoppingstatement=>stoppingstatement;等吗?)
supercat

我的语法不好,但是我相信,是的。但是,“停止”可以是本地的,例如循环内的“ break”语句。同样,如果方法无效,则方法的结尾是方法级别的隐式停止语句。“ throw”也将是一个明确的停止声明。
Pawel Veselov

Java标准对Javavoid blah() { try {return;} catch (RuntimeError ex) { } DoSomethingElse(); } 会发出这样的说法:Java发出嘘声,说没有办法try实际上可以通过异常退出该块,或者它认为任何未经检查的异常都可能在任何try块内发生?
2013年

找出答案的最佳方法是尝试对其进行编译。但是编译器甚至允许空的try / catch语句。但是,任何JVM指令理论上都可以引发异常,因此try块可能会退出。但是我不确定这与这个问题有什么关系。
Pawel Veselov

基本上,问题是唯一禁止的“不可达”语句是那些可以在解析的基础上被识别为不可达的语句,而不必实际评估任何表达式,还是该标准是否可以禁止诸如之类的不可达语句if (constantThatEqualsFive < 3) {doSomething;};
2013年

5

编译器的目标之一是排除错误类别。偶然地有一些无法访问的代码,javac在编译时排除了此类错误是很不错的。

对于捕获错误代码的每个规则,有人会希望编译器接受它,因为他们知道自己在做什么。那是编译器检查的代价,而正确地实现平衡是语言设计的关键点之一。即使经过最严格的检查,仍然可以编写无限数量的程序,因此事情不会那么糟。


5

尽管我认为此编译器错误是件好事,但有一种解决方法。使用您知道会成立的条件:

public void myMethod(){

    someCodeHere();

    if(1 < 2) return; // compiler isn't smart enough to complain about this

    moreCodeHere();

}

编译器不够聪明,无法对此抱怨。


6
这里的问题是为什么?无法访问的代码对编译器毫无意义。因此,它的唯一受众是开发人员。使用评论。
SamStephens

3
所以我不赞成投票,是因为我同意Java专家对它们进行编译的方式吗?哎呀...
肖恩·帕特里克·弗洛伊德

1
您因不回答实际问题而感到沮丧。参见SamStephens的评论。
BoltClock

2
javac绝对可以推断出这1<2是真的,并且该方法的其余部分不必包含在字节码中。“无法到达的语句”的规则必须清晰,固定且独立于编译器的灵巧性。
无可争议的2010年

1
@Sam以这种态度,您将不得不对本网站的50%最佳答案投反对票(我并不是说我的答案在其中)。在任何IT咨询情况下,回答SO的很大一部分是从给定问题中识别出真正的问题(或相关的附带问题)。
肖恩·帕特里克·弗洛伊德

0

抱怨编译器越严格越好,这当然是一件好事,只要它允许您执行所需的操作即可。通常,付出的小代价是注释掉代码,而好处是在编译代码时有效。一个典型的例子是Haskell,人们在知道自己的测试/调试只是主要测试而又是简短测试之前大喊大叫。我个人在Java中几乎(实际上是故意)不专心地进行调试。


0

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

另外两种可能很有趣的方式,但不适用于关闭方法的部分代码以及if (true) return

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

也有System.exit(0)办法,但是这可能是一个矫kill过正,如果将它放在Java中的JSP中,则它将终止服务器。

除了Java是一种设计为“保姆”的语言外,我宁愿使用诸如C / C ++之类的本机进行更多控制。


将某些内容从static final静态初始化块中设置的static final变量更改为声明中设置的变量,这不是一个重大更改。完全合理的是,当第一次编写一个类时,它有时可能无法执行某些操作(并且直到运行时才知道它是否能够执行该操作),但是该类的更高版本可能能够执行某些操作。始终执行该操作。更改myClass.canFoo为常数应if (myClass.canFoo) myClass.Foo(); else doSomethingElse();提高效率,而不是破坏它。
2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.