考虑这个功能:
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会发出警告。
答案 0 :(得分:44)
因为就编译器而言,System.exit()
只是另一种方法调用。
它所做的是结束这个过程的事实只能从实现中找到(这是本机代码,而不是它有任何区别)。
如果你必须在你的代码中添加System.exit()
(通常它最好避免它,除非你想要返回0以外的代码),它应该在一个返回{的方法中{1}},void
例如。它的方式更好。
至于可达性,解释是相同的:main()
是Java语言的关键字,因此IDE使用的编译器或解析器可以告诉它在理论上不可能在代码之后要执行的return
语句。这些规则定义为here。
答案 1 :(得分:13)
Java编译器对System.exit
一无所知。就这个问题而言,它只是一种方法 - 所以语句的结尾是可以达到的。
您说 L1
和L2
“明显无法访问”,但仅,因为您知道System.exit
做了什么。语言没有 - 而确实知道return
语句的作用,因此它知道L3
确实无法访问。
我有时认为能够声明方法不仅仅是void
,而从不正常终止 - 它永远不会返回(尽管它可能会抛出例外)。然后,编译器将能够使用该信息来使任何调用表达式的结束无法访问,从而防止出现此类问题。然而,这只是我对语言设计的梦想 - Java 没有有类似的东西,编译器“知道”特定的JRE方法永远不会正常返回是一个非常糟糕的主意,这个概念不能直接在语言中表达。
相反,编译器受section 14.21 of the JLS规则的约束,包括:
- 如果块可以访问,则非空块中不是交换块的第一个语句是可以访问的。
- 如果S之前的语句可以正常完成,则非空交换块中的每个其他语句S都是可到达的。
...
如果表达式语句可以访问,则表达式语句可以正常完成。
(方法调用是表达式语句。)
然后从第8.4.7节开始:
如果声明方法具有返回类型,则如果方法的主体可以正常完成(第14.1节),则会发生编译时错误。
和14.1:
除非另有说明,否则语句会正常完成,如果它评估的所有表达式以及它执行的所有子语句都正常完成。
因此,就编译器而言,对System.exit()
的调用可以正常完成,这意味着foo
方法的主体可以正常完成,从而导致错误。
答案 2 :(得分:7)
从语言角度来看,只有两种方法可以逃避当前范围:return
和throw
。方法调用永远不会被认为是相同的方式,即使它们只包含代码行:
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
...
对于你的问题,逻辑是一样的。
答案 3 :(得分:7)
我:“在退货声明之后可以执行任何事情吗?”
Java:“不。”
我:“我可以在调用System.exit后执行任何操作吗?”
Java:“这是一种方法,我不知道它做了什么 - 它不是一个保留的关键字,据我所知,它不会影响程序流程”(它甚至可能不起作用(我不知道退出可以抛出异常(或它的未来变体)))
答案 4 :(得分:2)
如果声明一个方法返回一个非void值,那么必须在某处包含return
语句,即使它从未到过(如问题中的代码)
从编译器的角度来看,System.exit()
只是另一种方法调用,没有任何特殊之处,表明程序一到达就结束了。只有你作为程序员才知道这个事实 - 但它不属于编译器的知识。
关于问题的第二部分 - 在方法内的代码块中的return
语句之后没有任何内容可以执行,因为它始终是不可缓存的代码。这就解释了为什么编译器抱怨L3线。
答案 5 :(得分:2)
编译器检查某些代码是否只能与return关键字一起访问(通常也是throw和break(在循环的情况下))。对于编译器,exit方法调用只是另一个调用,它不知道它的含义,所以它不知道之后的代码永远不会到达。
答案 6 :(得分:2)
我知道这没有意义,当你调用一个方法时,这仍然是Java编译器的工作方式。
编译器此时不知道Sytem.exist做了什么(在这种意义上,为什么rt.jar应该与你编译的其他jar不同?)。
这与下一段代码形成对比 -
public int test() {
throw new NullPointerException("aaaa");
}
编译器可以告诉始终抛出异常,因此不需要返回。
答案 7 :(得分:0)
静态代码分析工具可能没有考虑应用程序正在终止。