Eclipse中无法访问的代码错误与Java中的死代码警告?

有谁知道为什么:

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)”运行时等价物将被删除,并且两个程序的表示forms相同。 那么Java编译器会不会再次应用其可达的代码分析?

第一个不编译(你有错误),第二个编译(你刚刚得到一个警告)。 这是不同的。

至于为什么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说:

公共类Test扩展java.lang.Object {
 public Test();
  码:
    0:aload_0
    1:调用特定的#1;  //方法java / lang / Object。“”:()
    4:回报

 public void foo();
  码:
    0:getstatic#2;  // java / lang / System.out:Ljava / io / PrintStream;
    3:ldc#3;  // String foo
    5:invokevirtual#4;  //方法java / io / PrintStream.println:(Ljava / lang / StrV
    8:返回

 public void bar();
  码:
    0:getstatic#2;  // java / lang / System.out:Ljava / io / PrintStream;
    3:ldc#5;  //string
    5:invokevirtual#4;  //方法java / io / PrintStream.println:(Ljava / lang / String;)V
    8:getstatic#2;  // java / lang / System.out:Ljava / io / PrintStream;
    11:ldc#5;  //string
    13:invokevirtual#4;  //方法java / io / PrintStream.println:(Ljava / lang / String;)V
    16:回报

 }

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

根据Java语言规范,无法访问的代码是错误的。

引用JLS的话:

这个想法是,从构造函数,方法,实例初始值设定项或包含语句的静态初始值设定项开始,必须有一些可能的执行path。 分析考虑了陈述的结构。 除了while,do的特殊处理,对于状态expression式为常数值的语句,expression式的值在stream程分析中不予考虑。

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

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

那么突然它不会编译了,因为if语句没有path可以让最后一行到达。

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

作为一个例子,下面的语句会导致编译时错误:

 while (false) { x=3; } 

因为声明x = 3; 不可达; 但表面上类似的情况:

 if (false) { x=3; } 

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

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

第二块是“代码异味”, if (false)块通常被放入代码以禁用代码进行debugging,留下代码通常是偶然的,因此是警告。

事实上,Eclipse进行了更高级的testing,以确定if语句的可能值,以确定是否可以采用这两个path。 例如,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语句正在testing相同的东西,但是如果中间有10-15个代码行,它可能就不再那么明显了。

总之,两者之间的区别在于:一个是JLS禁止的,一个不是,而是被Eclipse检测为服务程序员。

这是为了允许一种有条件的编译
if不是错误,但是编译器会为whiledo-whilefor标记一个错误。
还行吧:

 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末尾解释:无法达到的声明 :

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

  static final boolean DEBUG = false; 

然后编写代码如:

  if (DEBUG) { x=3; } 

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

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

有一个有条件的意味着有可能改变的机会条件。 有些情况下,在圆括号中比true情况更为复杂的是,对于读者来说,下面的代码是“消极的”,但是编译器注意到,所以它能够警告你。

在这里提到了Eclipse,它使得用户看起来更复杂一些。 但实际上在Eclipse下是一个(非常复杂的)Java编译器,它正好具有很多用于警告的开关,Eclipse可以打开和closures。 换句话说,你不会从一个直接的javac编译中获得不同的警告/错误的广度,也没有方便的方法来打开或closures所有的警告/错误。 但是这是一样的交易,只是更多的花里胡哨。

我认为有一个办法是,无法访问的代码很可能是一个错误,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常量是完全内联的,并且在function上等同于只要在条件中inputtrue即可。 从JLS的angular度来看,这两个案例非常相似。

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

在你的第一个例子中,这是一个错误,因为java的定义是非法的。 编译器不允许你创build带有不可达语句的字节码。

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

对于一个典型的“IF”条件编译代码,JDK检测到这个并没有将其报告为死代码。 对于由一个常量布尔型标志引起的死代码,JDK会检测到这个错误并以警告级别进行报告。 对于由程序的控制stream程导致的死代码,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"); } 

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

  1. 单击Window-Preferences-Java-Compiler-Errors / Warnings
  2. 点击“潜在的编程问题”
  3. select“忽略”“死码,例如,如果(假)”
  4. 点击应用
  5. 点击OK

保存并closures你的eclipse IDE当你重新打开eclipse时,这些特定的警告应该不再列出。

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

Interesting Posts