35

考虑这个函数:

public boolean foo(){
   System.exit(1);
   //The lines beyond this will not be read
   int bar = 1;                  //L1
   //But the return statement is required for syntactically correct code
   return false;                 //L2

   //error here for unreachable code
   //int unreachable = 3;        //L3

}

有人可以解释为什么 L1 和 L2 明显无法到达不会发出警告,但 L3 会发出警告。

4

8 回答 8

49

因为就编译器而言,System.exit()只是另一个方法调用。

它所做的是结束进程的事实只能从实现中找到(这是本机代码,而不是它有任何区别)。

如果你必须System.exit()输入你的代码(通常最好避免它,除非你想返回一个不是 0 的代码),它应该真的在一个返回的方法中voidmain()例如。这样更好。

至于可达性,解释是一样的:是Java语言的一个关键字,所以IDE使用的编译器或者解析器可以判断语句return后面的代码理论上是不可能执行的。return这些规则在这里定义。

于 2012-07-14T20:36:55.750 回答
15

Java 编译器对System.exit. 就它而言,这只是一种方法 - 所以语句的结尾是可以到达的。

这么L1并且L2“明显无法到达”,但这只是因为您知道做什么System.exit。该语言不知道 - 而它确实知道return语句的作用,因此它知道L3确实无法访问。

我有时认为能够声明一个方法不仅仅是void,而且永远不会正常终止是很有用的——它永远不会只是返回(尽管它可能会抛出异常)。然后,编译器将能够使用该信息使任何调用表达式的结尾无法访问,从而防止此类事情成为问题。然而,这只是我对语言设计的梦想——Java没有任何类似的东西,当无法表达该概念时,编译器“知道”特定的 JRE 方法将永远不会正常返回,这将是一个非常糟糕的主意直接在语言中。

相反,编译器受JLS 第 14.21 节的规则约束,包括:

  • 非空块中不是 switch 块的第一条语句是可到达的,前提是该块是可到达的。
  • 如果 S 之前的语句可以正常完成,则非空块中不是 switch 块的所有其他语句 S 都是可访问的。

...

一个表达式语句可以正常完成,只要它是可达的。

(方法调用是一个表达式语句。)

然后从第 8.4.7 节:

如果将方法声明为具有返回类型,则如果方法的主体可以正常完成(第 14.1 节),则会发生编译时错误。

在 14.1 中:

除非另有说明,否则如果它评估的所有表达式和它执行的所有子语句都正常完成,则该语句将正常完成。

System.exit()所以对编译器的调用可以正常完成,这意味着方法的主体foo可以正常完成,从而导致错误。

于 2012-07-14T20:36:49.023 回答
9

我:“return 语句之后可以执行任何操作吗?”
爪哇:“没有。”

我:“调用 System.exit 后可以执行任何操作吗?”
Java:“那是一种方法,我不知道它的作用——它不是保留关键字,据我所知它不会影响程序流程”(它甚至可能不起作用(我不知道是否退出可以抛出异常(或它的未来变体)))

于 2012-07-14T20:41:58.253 回答
7

从语言的角度来看,只有两种方法可以逃避当前范围:returnthrow. 方法调用永远不会被视为相同的方式,即使它们由唯一的代码行组成:

void method() {
  throw new RuntimeException();
}

更。理论上,任何方法调用都可能导致RuntimeException被抛出。在这种情况下,编译器可能应该对任何不在try块内的方法调用发出警告:

...
method(); // WARNING: may throw in theory
anotherMethod(); // WARNING: possible unreachable code, also may throw
anotherMethod2(); // WARNING: possible unreachable code, also may throw
// etc
...

对于您的问题,逻辑是相同的。

于 2012-07-14T20:36:45.810 回答
2

如果一个方法被声明为返回一个非 void 值,那么它必须在某处包含一个return语句,即使它从未到达过(就像问题中的代码一样)。

从编译器的角度来看,System.exit()这只是另一个方法调用,没有什么特别之处,表明程序一到达就结束。只有你,作为程序员,知道这个事实——但它超出了编译器的知识范围。

关于您问题的第二部分-return方法内的代码块中的语句后面没有任何内容,因为那将始终是无法访问的代码。这就解释了为什么编译器抱怨 L3 行。

于 2012-07-14T20:36:59.557 回答
2

编译器检查某些代码是否仅针对 return 关键字是可访问的(通常还有 throw 和 break(在循环的情况下))。对于编译器来说,exit方法调用只是另一个调用,它不知道它的含义,所以它不知道后面的代码永远不会到达。

于 2012-07-14T20:37:10.137 回答
2

我知道这没有意义,当您调用方法时,Java 编译器仍然是这样工作的。
编译器此时不知道 Sytem.exist 做了什么(从这个意义上说,为什么 rt.jar 与您编译的其他 jar 不同?)。
例如,这与下一段代码形成对比 -

 public int test() {
  throw new NullPointerException("aaaa");
}

编译器可以判断总是抛出异常,因此不需要返回。

于 2012-07-14T20:40:15.120 回答
0

静态代码分析工具可能没有考虑到应用程序正在终止。

于 2012-07-14T20:36:59.070 回答