有谁知道原因:
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编译器不会再次应用其可访问的代码分析吗?
答案 0 :(得分: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
说:
public class Test extends java.lang.Object{ public Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public void foo(); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String foo 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/StrV 8: return public void bar(); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #5; //String bar 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 11: ldc #5; //String bar 13: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 16: return }
至于为什么它(Sun)没有给出警告,我不知道:)至少JDK编译器实际上内置了DCE(死代码消除)。
答案 1 :(得分:25)
根据Java Language Spec,无法访问的代码是错误。
引用JLS:
这个想法是从构造函数,方法,实例初始化器或包含语句本身的静态初始化器开始必须有一些可能的执行路径。分析考虑了陈述的结构。除了对while,do和for条件表达式具有常量值true的语句的特殊处理外,在流分析中不考虑表达式的值。
这意味着,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禁止的,一个不是,但Eclipse会将其检测为程序员的服务。
答案 2 :(得分: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,然后正确编译代码而不对程序文本进行其他更改。
答案 3 :(得分:2)
if (true)
比“无法到达”更微妙;因为硬编码的return
将始终使以下代码无法访问,但更改if
中的条件可能会使以下语句可达。
有条件意味着有机会条件可能改变。在某些情况下,括号中会出现比true
更复杂的情况,并且人类读者并不明白以下代码是“被禁止的”,但编译器会注意到,因此它可以向您发出警告。
这里提到了Eclipse,它使用户看起来有点复杂;但实际上Eclipse下面只是一个(非常复杂的)Java编译器,碰巧有很多开关用于警告,Eclipse可以打开和关闭。换句话说,你没有得到直接javac
编译中不同警告/错误的广度,也没有方便的方法来打开或关闭所有这些警告/错误。但这是同样的交易,只是有更多的花里胡哨。
答案 4 :(得分: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的角度来看,这两个案例非常相似。
答案 5 :(得分:0)
不同之处在于运行时和编译时之间的语义。 在第二个示例中,代码编译为字节码中的if-else分支,而eclipse非常智能,足以告诉您在运行时永远不会到达else部分。 Eclipse只会警告您,因为它仍然是合法代码。
在您的第一个示例中,这是一个错误,因为java的定义代码是非法的。编译器不允许您使用无法访问的语句创建字节代码。
答案 6 :(得分: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");
}
答案 7 :(得分:0)
如果您希望忽略Eclipse中的警告" Java中的死代码警告"在eclipse *中执行以下操作:
保存并关闭eclipse IDE当您重新打开eclipse时,不应再列出这些特定警告。
*对于此示例解决方案,我使用Eclipse IDE for Java Developers - Version:Mars.2 Release(4.5.2)