53

有谁知道为什么:

public void foo()
{
    System.out.println("Hello");
    return;
    System.out.println("World!");
}

在 Eclipse 下会被报告为“无法访问的错误”,但是

public void foo()
{
    System.out.println("Hello");
    if(true) return;
    System.out.println("World!");
}

只触发“死代码”警告?

我能想到的唯一解释是 Java 编译器只标记了第一个,而 Eclipse 中的一些额外分析找出了第二个。但是,如果是这样,为什么 Java 编译器不能在编译时找出这种情况呢?

Java 编译器不会在编译时发现 if(true) 没有效果,从而产生本质上相同的字节码吗?在什么时候应用可达代码分析?

我想考虑这个问题的更一般的方法是:“何时应用可达代码分析”?在将第二个 Java 代码片段转换为最终字节码的过程中,我确信在某些时候“if(true)”运行时等效项被删除,并且两个程序的表示变得相同。Java 编译器不会再次应用其可达代码分析吗?

4

8 回答 8

34

第一个不编译(你得到一个错误),第二个编译(你只是得到一个警告)。这就是区别。

至于为什么 Eclipse 会检测到死代码,嗯,这只是集成开发工具的便利性,它带有内置编译器,与 JDK 相比,它可以进行更多的微调来检测这种代码。

更新:JDK 实际上消除了死代码。

public class Test {
    public void foo() {
        System.out.println("foo");
        if(true)return;
        System.out.println("foo");
    }
    public void bar() {
        System.out.println("bar");
        if(false)return;
        System.out.println("bar");
    }
}

javap -c说:

公共类测试扩展 java.lang.Object{
公共测试();
  代码:
   0:aload_0
   1:调用特殊#1;//方法 java/lang/Object."":()V
   4:返回

公共无效 foo();
  代码:
   0:获取静态#2;//字段 java/lang/System.out:Ljava/io/PrintStream;
   3:最不发达国家#3;//字符串 foo
   5:调用虚拟#4;//方法java/io/PrintStream.println:(Ljava/lang/StrV
   8:返回

公共无效栏();
  代码:
   0:获取静态#2;//字段 java/lang/System.out:Ljava/io/PrintStream;
   3:最不发达国家#5;//字符串条
   5:调用虚拟#4;//方法java/io/PrintStream.println:(Ljava/lang/String;)V
   8:获取静态#2;//字段 java/lang/System.out:Ljava/io/PrintStream;
   11:最不发达国家#5;//字符串条
   13:调用虚拟#4;//方法java/io/PrintStream.println:(Ljava/lang/String;)V
   16:返回

}

至于为什么它(Sun)没有给出警告,我不知道:) 至少 JDK 编译器实际上内置了 DCE(死代码消除)。

于 2010-01-26T17:04:22.333 回答
25

根据Java Language Spec ,无法访问代码是一个错误。

引用 JLS 的话:

这个想法是,从包含语句的构造函数、方法、实例初始化程序或静态初始化程序的开头到语句本身,必须有一些可能的执行路径。该分析考虑了语句的结构。除了特殊处理while、do和条件表达式为真值的语句外,流分析中不考虑表达式的值。

这意味着,if没有考虑该块,因为如果您通过if语句的路径之一,您可以到达最终的打印语句。如果您将代码更改为:

public void foo() {
    System.out.println("Hello");
    if (true)
        return;
    else
        return;
    System.out.println("World!");
}

然后突然它不再编译了,因为没有通过if语句的路径可以到达最后一行。

也就是说,Java 兼容的编译器不允许编译您的第一个代码片段。进一步引用 JLS:

例如,以下语句会导致编译时错误:

while (false) { x=3; }

因为语句 x=3; 无法到达;但表面上类似的情况:

if (false) { x=3; }

不会导致编译时错误。优化编译器可能会意识到语句 x=3; 将永远不会被执行,并且可以选择从生成的类文件中省略该语句的代码,但是语句 x=3; 在此处指定的技术意义上,不被视为“无法访问”。

Eclipse 给出的关于死代码的第二个警告是编译器生成的警告,根据 JLS,这不是“无法访问”,但实际上是。这是 Eclipse 提供的附加lint样式检查。这完全是可选的,并且通过使用 Eclipse 配置,可以禁用,或者变成编译器错误而不是警告。

第二个块是“代码气味”,if (false)通常会放入块以禁用代码以进行调试,将其留在后面通常是偶然的,因此会发出警告。

事实上,Eclipse 进行了更高级的测试来确定 if 语句的可能值,以确定是否可以采用两条路径。例如,Eclipse 也会在以下方法中抱怨死代码:

public void foo() {
    System.out.println("Hello");
    boolean bool = Random.nextBoolean();
    if (bool)
        return;
    if (bool || Random.nextBoolean())
      System.out.println("World!");
}

它将为第二个 if 语句生成一个无法访问的代码,因为它可以推断该代码bool必须仅false在代码中的这一点上。在如此短的代码片段中,很明显两个 if 语句正在测试相同的东西,但是如果中间有 10-15 行代码,它可能不再那么明显了。

综上所述,两者的区别是:一种是被 JLS 禁止的,一种是不被 JLS 禁止的,而是被 Eclipse 检测为为程序员提供的服务。

于 2010-01-26T17:36:36.767 回答
14

这是允许一种有条件的编译
这不是错误if,但编译器会标记while,do-while和的错误for
还行吧:

if (true) return;    // or false
System.out.println("doing something");

这是错误

while (true) {
}
System.out.println("unreachable");

while (false) {
    System.out.println("unreachable");
}

do {
} while (true);
System.out.println("unreachable");

for(;;) {
}
System.out.println("unreachable");

在JLS 14.21: Unreachable Statements的末尾有解释:

这种不同处理的基本原理是允许程序员定义“标志变量”,例如:

 static final boolean DEBUG = false;

然后编写代码,例如:

   if (DEBUG) { x=3; }

这个想法是应该可以将 DEBUG 的值从 false 更改为 true 或从 true 更改为 false,然后正确编译代码,而不需要对程序文本进行其他更改。

于 2010-01-26T18:15:16.757 回答
2

if (true)比“无法到达”更微妙一些;因为硬编码return总是会使下面的代码无法访问,但是更改中的条件if可以使下面的语句可以访问。

有条件意味着有机会条件可能会改变。在某些情况下,括号中包含比 a 更复杂的true内容,并且对于人类读者来说,以下代码“已死”并不明显,但编译器会注意到,因此它能够警告您。

这里提到了 Eclipse,它让用户觉得事情有点复杂;但实际上在 Eclipse 之下只是一个(非常复杂的)Java 编译器,它恰好具有许多用于警告等的开关,Eclipse 可以打开和关闭这些开关。换句话说,您不会从直接javac编译中获得相当广泛的不同警告/错误,也没有方便的方法来打开或关闭所有这些警告/错误。但这是同样的交易,只是有更多的花里胡哨。

于 2010-01-26T17:05:02.517 回答
2

我认为解决问题的一种方法是无法访问的代码很可能是一个错误,而 JLS 试图保护您免受此类错误的影响。

如果您真的想故意这样做,允许if (true) return;是解决 JLS 限制的好方法。如果 JLS 阻止了这一点,那将成为阻碍。此外,它还应该停止:

 public static boolean DEBUG = true; //In some global class somewhere else


 ...

 if (DEBUG) return; //in a completely unrelated class.

 ...

因为 DEBUG 常量是完全内联的,并且在功能上等同于在 if 条件中键入一个 true。从 JLS 的角度来看,这两种情况非常相似。

于 2010-01-26T17:47:23.263 回答
0

区别在于运行时和编译时之间的语义。在您的第二个示例中,代码编译为字节码中的 if-else 分支,而 eclipse 足够聪明,可以告诉您在运行时永远不会到达 else 部分。Eclipse 只会警告您,因为它仍然是合法代码。

在您的第一个示例中,这是一个错误,因为该代码根据 java.util. ​​的定义是非法的。编译器不允许您使用无法访问的语句创建字节码。

于 2010-01-26T17:08:52.190 回答
0

我在eclipse上做了一些尝试,认为JDK的死代码处理有3种:1)没有警告,2)警告和3)错误。

对于典型的“IF”条件编译代码,JDK 会检测到这一点并且没有将其报告为死代码。对于由常量布尔标志导致的死代码,JDK 会检测到这一点并将其报告为警告级别。对于程序控制流导致的死代码,JDK 将其检测为错误。

下面是我的尝试:

    public class Setting {
        public static final boolean FianlDebugFlag = false;
    }


    class B {
    .....

    // no warn, it is typical "IF" conditional compilataion code
    if(Setting.FianlDebugFlag) 
        System.out.println("am i dead?");   
    if(false) 
        System.out.println("am i dead?");   


    // warn, as the dead code is caused by a constant boolean flag
    if(ret!=null && Setting.FianlDebugFlag) 
        System.out.println("am i dead?");   

    if(Setting.FinalDebug)                  
        return null;                                                            
    System.out.println("am i dea?");        

    // error, as the dead code is due to the program's control flow
    return null;
    System.out.println("am i dead");        
    }
于 2014-02-12T02:59:11.227 回答
0

如果您希望忽略“Eclipse 下 Java 中的死代码警告”警告,请在 eclipse* 中执行以下操作:

  1. 单击窗口-首选项-Java-编译器-错误/警告
  2. 点击“潜在的编程问题”
  3. 选择“忽略”“死代码,例如 if(false)”
  4. 单击应用
  5. 点击确定

保存并关闭您的 Eclipse IDE 当您重新打开 Eclipse 时,不应再列出这些特定的警告。

*对于这个示例解决方案,我使用 Eclipse IDE for Java Developers - 版本:Mars.2 Release (4.5.2)

于 2016-03-29T11:00:38.790 回答