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

2022-08-31 19:55:21

有谁知道为什么:

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编译器难道不会再次应用其可访问的代码分析吗?


答案 1

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

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

}

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


答案 2

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

引用JLS的话:

这个想法是,从构造函数,方法,实例初始值设定项或包含语句的静态初始值设定项的开头到语句本身,必须存在一些可能的执行路径。分析考虑了报表的结构。除了对 while、do 和条件表达式具有常量值 true 的语句的特殊处理外,在流分析中不考虑表达式的值。

这意味着不考虑块,因为如果您通过语句的其中一个路径,则可以到达最终的打印语句。如果您将代码更改为:ifif

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 语句生成一个无法访问的代码,因为它可以推理该代码中必须仅在此时。在如此短的代码片段中,很明显,两个if语句正在测试相同的内容,但是如果中间有10-15个代码行,它可能不再那么明显了。boolfalse

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


推荐