为什么在 System.exit(0) 之后还需要返回值?

36

考虑这个函数:

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 会吗?


1
哪个编译器会给出那个警告? - Thorbjørn Ravn Andersen
1
@ThorbjørnRavnAndersen 在 Eclipse 中使用 Java 编译器 - Nitin Chhajer
@NitinChhajer,你在使用Eclipse吗? - user1329572
7
没有人阻止你将System.exit()修改为NOP(无操作)。现在,它可能会抛出安全异常或线程终止异常。 - bestsss
8个回答

50
因为就编译器而言,System.exit() 只是另一个方法调用。
它结束进程的事实只能从实现中找到(这是本地代码,不过这并没有什么区别)。
如果你必须在代码中放置 System.exit()(通常最好避免,除非你想返回一个不为0的代码),它应该真正地在一个返回 void 的方法中,例如 main()。这样更好。
至于可达性,解释也相同:return 是 Java 语言的关键字,因此编译器或 IDE 使用的解析器可以告诉代码在 return 语句之后理论上不可能被执行。这些规则在 这里 定义。

3
实际上,您甚至可以调用另一种方法 foo(),该方法再调用 System.exit(..),这样我们就知道 foo() 永远不会返回,但编译器无法检查所有可能的情况。 - Andre Holzner
或者你甚至可以拥有自己的本地方法来完成相同的操作。 - biziclop
4
通常情况下不需要调用 System.exti() 方法。只需要从 main 方法中返回即可退出程序。 - Steve Kuo
3
不,应该始终使用System.exit()!如果你只是让程序自然死亡,有时会出现这个错误输出(在Windows、Java 1.8.0_74上尝试不到10次即可):"ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2 [etc.]" 这是一个已知的Java bug。此外,一些Swing组件(例如文件对话框)一旦被使用,就会防止程序永远自然死亡。例如,可以参考这里 - Dreamspace President
“它确实应该在返回void的方法中,比如main()。那样更好。”- 如果出现Guice的关键ProvisionException时,我需要停止应用程序怎么办?在这种情况下使用ThrowingProviders值得吗(这并不容易,因为我不能像这样导入新库)?或者你有更好的建议吗? - Line
1
这是 Java 的一个糟糕的设计。它可能会引入另一种返回类型,比如“never”。返回类型表示该函数永远不会返回(无论是死循环还是停止程序)。 - tsh

15
Java编译器对于System.exit一无所知,它只是一个方法,因此语句的结尾是可达的。你说L1和L2“明显不可达”,但这仅仅是因为你知道System.exit的作用,而语言并不知道。然而,语言确实知道return语句的作用,因此它知道L3确实是不可达的。有时我认为声明一个方法不仅仅是void,而是永远不会正常终止(虽然可能会抛出异常)会很有用。编译器将能够使用这些信息使任何调用表达式的结尾都不可达,从而防止这种问题的发生。然而,这只是我对语言设计的梦想而已——Java没有类似的功能,而且编译器“知道”特定JRE方法永远不会正常返回,当该概念不能直接在语言中表达时,这将是一个非常糟糕的想法。

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

  • 非空块中不是switch块的第一条语句只有在该块可达时才可达。
  • 非空块中不是switch块的每个其他语句S只有在其前面的语句可以正常完成时才可达。

...

“表达式语句可正常完成当且仅当它是可到达的。”(一个方法调用是一个表达式语句。)然后从第8.4.7节:“如果声明一个方法有返回类型,则在方法体正常完成时会发生编译时错误(§14.1)。”在14.1中:“除非另有规定,否则如果它评估的所有表达式和执行的所有子语句都正常完成,则语句正常完成。”因此,对System.exit()的调用在编译器看来可以正常完成,这意味着foo方法的主体可以正常完成,这导致了错误。

10

我: "在return语句之后还能执行其他任何操作吗?"
Java: "不行。"

我: "在调用System.exit之后还能执行其他任何操作吗?"
Java: "那是一个方法,我不知道它做了什么 - 它不是保留关键字,据我所知它不会影响程序流程"(并且它可能甚至不起作用(我不知道exit是否会抛出异常(或者未来的变体)))


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
...

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


2
我知道这看起来没有意义,但这就是Java编译器在调用方法时的工作方式。
此时编译器不知道Sytem.exist是什么(为什么rt.jar与您编译的其他jar文件在这方面有所不同?)。
这与下面的代码片段形成对比-
 public int test() {
  throw new NullPointerException("aaaa");
}

当编译器可以确定异常总是被抛出时,就不需要返回值了。


2
如果一个方法声明返回非void值,则必须在其中包含一个return语句,即使它永远不会被执行(就像问题中的代码一样)。
从编译器的角度来看,System.exit()只是另一个方法调用,并没有任何特殊之处表明程序在达到此处时结束。只有你作为程序员知道这个事实,但这是超出编译器知识范围的事情。
关于你问题的第二部分 - 在方法内部的代码块中,return语句后面不能跟任何代码,因为那总是无法到达的代码。这就解释了为什么编译器会抱怨L3行。

2
编译器会检查代码是否可达,只有在考虑 return 关键字(同时对于循环中的 throw 和 break 也是如此)。对于编译器而言,exit 方法调用只是另一个调用,它不知道其意义,因此它不知道随后的代码永远不会被执行。

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接