不可到达的语句:while true vs if true

10

我该如何理解这个Java编译器的行为?

while (true) return;
System.out.println("I love Java");
// Err: unreachable statement

if (true) return;
System.out.println("I hate Java");
// OK.

谢谢。

编辑:

我在几分钟后找出了重点:

在第一种情况下,编译器由于无限循环而抛出错误。在两种情况下,编译器都不考虑语句结果内部的代码。

编辑 II:

现在让我印象深刻的是 javac:

    if (true) return; // Correct
}
    while (true) return; // Correct
}

看起来javac知道循环和if语句的内部内容,但是当您编写另一个命令(如第一个示例中)时,您会得到不同的行为(看起来javac忘记了循环/ if语句的内部内容)。

第三次编辑:根据这个答案的结果,我可以说一下(希望是正确的):对于Java,表达式if(arg){...;return;}while(arg){...;return;}在语义上和语法上(字节码)等效,当且仅当argv是非常量(或有效的最终类型)表达式。 如果argv是常量表达式,字节码(和行为)可能有所不同。

免责声明:本问题不涉及无法访问的语句,而是涉及逻辑等效表达式的不同处理,例如while true returnif true return


7
这是一个 "如果我写了疯狂的代码,编译器也可能表现得很疯狂" 的情况。 - Kayaman
1
@Andremoniy 不对。请仔细阅读。 - marek094
@marek094听起来像是OCJP或OCJD考试。尝试从JLS学习规则,只需要60%的分数即可通过(我想)。考试后你再也用不到了。 - k5_
1
@marek094 只有在语法正确的情况下,您才能拥有合理的语义。无法编译的代码可能具有语义意图,但没有实际语义。 - k5_
显示剩余4条评论
3个回答

14

在Java中,语句的可达性有相当严格的规则。这些规则旨在易于评估,而不是百分之百准确,应该可以防止基本的编程错误。如果要推理Java中的可达性,您必须遵守这些规则,"常见逻辑"并不适用。

因此,以下是来自Java语言规范14.21.不可到达语句的规则 14.21. Unreachable Statements

如果-然后语句可以正常完成,当且仅当它是可到达的。

因此,在没有else的情况下,if-then之后的语句始终是可到达的。

while语句可以正常完成,当且仅当以下至少一条为真:

  • while语句是可达的,并且条件表达式不是具有值true(§15.28)的常量表达式。

  • 存在一个可达的break语句退出while语句。

条件是一个常量表达式"true",没有break。因此它无法正常完成


2
我记得看到过这个的解释。基本思想是你可以有 if(COMPILATION_SWITCH) return; A 并且程序可以正确编译,无论开关的值如何。 - zch

5
根据文档:docs

除了条件表达式的常量值为 true 的 while、do 和 for 语句的特殊处理外,表达式的值在流程分析中不予考虑。


2

如果您稍微更改代码(删除常量表达式),以便不触发javac可达性,它实际上将为两者生成相同的字节码。

static boolean flag = true;

static void twhile(){
    while (flag) return;
    System.out.println("Java");
}
static void tif(){
    if (flag) return;
    System.out.println("Java");
}

生成的字节码:
  static void twhile();
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
      StackMap locals:
      StackMap stack:
     0: getstatic     #10                 // Field flag:Z
     3: ifeq          7
     6: return
      StackMap locals:
      StackMap stack:
     7: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
    10: ldc           #26                 // String Java
    12: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    15: return
      LineNumberTable:
    line 8: 0
    line 9: 7
    line 10: 15
      LocalVariableTable:
    Start  Length  Slot  Name   Signature
      StackMapTable: number_of_entries = 1
    frame_type = 7 /* same */

  static void tif();
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
      StackMap locals:
      StackMap stack:
     0: getstatic     #10                 // Field flag:Z
     3: ifeq          7
     6: return
      StackMap locals:
      StackMap stack:
     7: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
    10: ldc           #26                 // String Java
    12: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    15: return
      LineNumberTable:
    line 12: 0
    line 13: 7
    line 14: 15
      LocalVariableTable:
    Start  Length  Slot  Name   Signature
      StackMapTable: number_of_entries = 1
    frame_type = 7 /* same */

是的,我读取了常量表达式情况下的字节码,它们是不同的,相当晦涩。 - marek094
@marek094,由于您没有为常量表达式案例提供可编译的示例,因此很难进行比较。在我的测试中,它会创建带有不同填充的“noops”。 - k5_
你说得对,我指的是我问题中的第二个例子。我知道这不是同样的情况,但也很有趣。 - marek094

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