86

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

4

8 回答 8

66

因为无法访问的代码对编译器来说毫无意义。虽然使代码对人们有意义比使其对编译器有意义更重要也更难,但编译器是代码的基本消费者。Java 的设计者认为对编译器没有意义的代码是错误的。他们的立场是,如果您有一些无法访问的代码,那么您就犯了需要修复的错误。

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

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


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

单元测试无法捕捉到每一个错误。我们不会将此作为反对其价值的论据。同样,编译器无法捕获所有有问题的代码,但它仍然很有价值,可以在可能的情况下防止错误代码的编译。

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


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

于 2010-09-25T21:22:31.870 回答
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,然后正确编译代码,而无需对程序文本进行其他更改。

于 2010-09-25T22:47:16.253 回答
19

是保姆。我觉得 .Net 做对了——它会针对无法访问的代码发出警告,但不会引发错误。收到警告是件好事,但我认为没有理由阻止编译(尤其是在调试会话期间,可以很好地抛出 return 以绕过某些代码)。

于 2010-09-25T21:35:52.717 回答
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 等)来说,这通常不会成为问题。

于 2013-08-01T21:48:37.733 回答
5

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

对于每条捕获错误代码的规则,都会有人希望编译器接受它,因为他们知道自己在做什么。这是编译器检查的惩罚,而获得正确的平衡是语言设计的技巧之一。即使经过最严格的检查,仍然可以编写无限数量的程序,所以事情不会那么糟糕。

于 2010-09-25T22:02:16.433 回答
5

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

public void myMethod(){

    someCodeHere();

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

    moreCodeHere();

}

编译器不够聪明,无法抱怨这一点。

于 2010-09-25T21:36:57.987 回答
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)方法,但这可能有点过头了,如果你把它放在 JSP 中的 Java 中,那么它将终止服务器。

除了 Java 在设计上是一种“保姆”语言之外,我宁愿使用像 C/C++ 这样的原生语言来进行更多控制。

于 2012-06-08T21:50:18.130 回答
0

抱怨编译器越严格越好,这当然是一件好事,只要它允许您做您需要的事情。通常要付出的小代价是把代码注释掉,好处是当你编译你的代码时可以工作。一个普遍的例子是 Haskell,人们尖叫直到他们意识到他们的测试/调试只是主要测试和短测试。我个人在 Java 中几乎不进行调试,而(实际上是故意的)不专心。

于 2010-09-25T22:35:14.400 回答