为什么Java有"无法访问的语句"编译器错误?

Mik*_*ike 80 java language-design unreachable-statement

我经常发现在调试程序时,在代码块中插入一个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");
        }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Sam*_*ens 61

因为无法访问的代码对编译器没有意义.虽然使代码对人有意义对于使编译器有意义而言是最重要和最难的,但编译器是代码的基本消费者.Java的设计者认为对编译器没有意义的代码是错误的.他们的立场是,如果你有一些无法访问的代码,你就犯了一个需要修复的错误.

这里有一个类似的问题:无法访问的代码:错误或警告?作者在其中写道:"我个人认为它应该是一个错误:如果程序员编写了一段代码,那么它应该始终是为了在某些情况下实际运行它." 显然Java的语言设计者同意.

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


许多人在评论中指出,有许多类无法访问的代码Java不会阻止编译.如果我正确理解哥德尔的后果,那么编译器就无法捕获所有类别的无法访问的代码.

单元测试无法捕获每个bug.我们不会将此作为反对其价值的论据.同样,编译器无法捕获所有有问题的代码,但它仍然有助于防止编译坏代码.

Java语言设计者认为无法访问的代码是错误的.因此,尽可能防止编译是合理的.


(在你投票之前:问题不在于Java是否应该有一个无法访问的语句编译器错误.问题是为什么 Java有一个无法访问的语句编译器错误.不要因为你认为Java做出了错误的设计决定而对我进行了投票.)

  • _显然,Java的语言设计者同意"Java的语言设计者"是人类,也容易出错.就个人而言,我觉得你所提到的讨论中的另一方有更强烈的论据. (22认同)
  • SamStephens:看看未调用的方法和未使用的变量本质上是所有形式的无法访问的代码.为什么允许某些形式而不是其他形式 特别是,为什么不允许这样一个有用的调试机制呢? (10认同)
  • 问题是他们(java语言设计者)与此不一致.如果有实际的流量分析,那么实现两者的回归是微不足道的.System.out.println();`和`if(true){return; System.out.println();`保证具有相同的行为,但一个是编译错误,另一个不是.所以,当我正在调试时,我使用这种方法暂时"注释掉"代码,这就是我的工作方式.注释掉代码的问题在于你必须找到适当的地方来停止你的评论,这可能比投入`return;'真快. (10认同)
  • @Gabe:因为它不是无害的 - 这几乎肯定是一个错误.要么你把你的代码放在了错误的地方,要么误解你已经以某种方式编写了你的​​陈述,而这些陈述是无法达到的.将此错误设置为可防止编写错误代码,如果您希望代码中无法访问的内容可供其他开发人员阅读(仅针对无法访问的代码),请使用注释. (6认同)
  • 评论是否也无法达到?在我看来,无法访问的代码实际上是一种评论形式(这是我之前尝试做的,等等).但考虑到"真正的"评论也不"可达"......也许他们也应该提出这个错误;) (5认同)
  • @Mike:Java吐出[过多的编译时错误](http://mindprod.com/jgloss/compileerrormessages.html)并不总是与语法有关.例如,忘记覆盖抽象方法,尝试捕获从未从其try块抛出的异常等. (4认同)
  • 我觉得你错过了这一点.如果有无法访问的代码,那肯定是错的,但这并不意味着无论如何都无法执行代码,这在调试时非常有用.只要编译器在每次遇到这样的问题时仍然尖叫"警告",还不够吗? (3认同)
  • 我不认为这解释了为什么"无意义"的代码足以证明编译器错误,这通常会被保留用于语法问题. (2认同)
  • 使用未使用的代码不值得错误恕我直言.最多的警告会很好.如果我有未完成的事情怎么办?我不想因为它无法访问而删除它. (2认同)
  • 从编译器的角度来看毫无意义!=从开发人员的角度来看毫无意义.无法访问代码的编译错误是我最近看到的最愚蠢的事情. (2认同)
  • @SamStephens"它几乎肯定是一个错误"......"几乎可以肯定"不是很确定,对于"可能是错误"的事情,编译器有警告.函数中间的普通返回几乎从来不是一个错误,它是故意无意间完成的.我们为什么不使用评论?注释不会嵌套,因此如果已经存在其他块注释,则不能将其余的函数括在块注释中.你必须编写一些愚蠢的解决方法,比如`if(true)return;`看起来更像是一个错误,不会产生任何警告,最终可能会被遗忘. (2认同)

irr*_*ble 46

没有明确的理由说明为什么不能允许无法访问的陈述; 其他语言允许他们没有问题.根据您的具体需要,这是通常的技巧:

if (true) return;
Run Code Online (Sandbox Code Playgroud)

它看起来很荒谬,任何读取代码的人都会猜测它必须是故意做的,而不是一个粗心的错误,让其余的语句无法访问.

Java对"条件编译"有一点支持

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

if (false) { x=3; }
Run Code Online (Sandbox Code Playgroud)

不会导致编译时错误.优化编译器可以实现语句x = 3; 将永远不会执行,并可能选择从生成的类文件中省略该语句的代码,但语句x = 3; 在此处指定的技术意义上,不被视为"无法到达".

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

static final boolean DEBUG = false;
Run Code Online (Sandbox Code Playgroud)

然后编写如下代码:

if (DEBUG) { x=3; }
Run Code Online (Sandbox Code Playgroud)

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

  • @SamStephens但是每当你想要切换时,你必须记住你必须注释代码的所有地方以及你必须做相反的事情.您必须阅读整个文件,更改源代码,可能偶尔会出现一些微妙的错误,而不是仅仅在一个地方用"false"替换"true".我认为最好对开关逻辑进行一次编码,除非必要,否则不要触摸它. (8认同)
  • 这个技巧对于调试非常有用.我经常添加一个if(true)返回来消除一个方法的"其余部分",试图找出它失败的地方.还有其他方法,但这很干净,很快 - 但这不是你应该检查的东西. (5认同)
  • 仍然不明白你为什么要打扰你展示的技巧.无法访问的代码对编译器来说毫无意义.因此,它的唯一受众是开发人员.使用评论.虽然我猜你是否暂时添加了一个回报,但使用你的解决方法可能比仅仅回归更快,并且注释掉下面的代码. (2认同)

Bra*_*itz 18

这是保姆.我觉得.Net得到了这个 - 它引发了对无法访问的代码的警告,但不是错误.关于它的警告是好的,但我认为没有理由阻止编译(特别是在调试会话期间,抛出一个返回以绕过一些代码是很好的).

  • 编译器也很容易排除无法访问的代码.没有理由强迫开发者这样做. (6认同)
  • @SamStephens如果你已经有评论,那不是因为评论不会叠加,所以用评论解决这个问题是不必要的. (2认同)

Paw*_*lov 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);
Run Code Online (Sandbox Code Playgroud)

在第3行,JVM将检查'if'的两个分支是否只存储到一个(只是本地var#0)与Object兼容的东西(因为这就是来自第3行和第3行的代码将如何处理本地var#0 ).

当编译器到达无法访问的代码时,它不太清楚堆栈可能在该点处的状态,因此无法验证其状态.它不能完全编译代码,因为它无法跟踪局部变量,因此它不会在类文件中留下这种歧义,而是产生致命的错误.

当然一个简单的条件就像if (1<2)愚弄它,但它并不是真的很愚蠢 - 它给它一个潜在的分支,可以导致代码,至少编译器和VM都可以确定如何从那里使用堆栈项上.

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


Sea*_*oyd 5

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

public void myMethod(){

    someCodeHere();

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

    moreCodeHere();

}
Run Code Online (Sandbox Code Playgroud)

编译器不够聪明,不能抱怨.

  • 这里的问题是为什么?无法访问的代码对编译器来说毫无意义.因此,它的唯一受众是开发人员.使用评论. (6认同)
  • 所以我得到了投票,因为我同意java家伙设计编译的方式?哎呀... (3认同)
  • javac绝对可以推断出`1 <2`是真的,并且该方法的其余部分不必包含在字节代码中."无法达到的陈述"的规则必须明确,固定,并且独立于编译器的智能性. (2认同)

Ric*_*son 5

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

对于捕获错误代码的每个规则,有人会希望编译器接受它,因为他们知道他们在做什么.这是编译器检查的代价,并且获得正确的平衡是语言设计的诡计之一.即使经过最严格的检查,仍然可以编写无数个程序,所以事情也不会那么糟糕.