为什么Eclipse生成的equals()实现在类型检查(instanceof)之前会检查null?

15

我经常使用Eclipse代码生成工具(Source/Generate hashCode()和equals()...)为简单的POJO类创建equals()实现。如果我选择“使用instanceof比较类型”,这将产生一个类似于以下内容的equals()实现:

我经常使用Eclipse的代码生成工具 (Source / Generate hashCode() 和 equals() ...) 为简单的 POJO 类创建 equals() 实现。如果我选择 "Use instanceof to compare types",则会生成类似于以下的 equals() 实现:

  @Override
  public boolean equals(Object obj) {
      if (this == obj) {
          return true;
      }
      if (obj == null) {
          return false;
      }
      if (!(obj instanceof MyClass)) {
          return false;
      }
      MyClass other = (MyClass) obj;
      // check the relevant fields for equality
  }

今天一位同事指出,第二个if语句根本不必要,因为当obj为空时instanceof类型检查将返回false。(参见问题3328138)

现在,我想那些为Eclipse JDT编写代码模板的人也是很有价值的。所以我觉得肯定有某些原因使得null检查必要,但我不太确定是什么原因?

(此外,问题7570764可能会给出一些提示:如果我们使用getClass()比较来进行类型检查而不是使用instanceof,那么obj.getClass()就不安全了。也许代码模板只是不够聪明,不能在我们使用instanceof时省略null检查。)

编辑:Dragan在他的回答中注意到,instanceof类型检查不是Eclipse的默认设置,所以我已经将其从问题中删去。但这并没有改变什么。

请也不要建议我使用getClass()或(更好的是!)其他IDE。这不是重点,也不是回答问题。我没有问如何编写equals()实现,是否使用instanceof或getClass()等。

问题大致是:这是Eclipse中的一个小bug吗?如果不是,为什么它能够作为一个功能被认可?


不知道,但 instanceof 并不总是被认为是最好的选择。 另一种方法是比较 obj.getClass() 和 this.getClass(),然后你需要进行 null 检查。 - Stultuske
我会引用 这个答案的评论。具体来说,在第8项中,他指出在equals()方法中,一个instanceof运算符有两个目的 - 它验证参数既不为空,又是正确的类型。 "...[所以你不需要单独的空检查。" - SomeJavaGuy
IntelliJ Idea 生成一个包含 null 检查的 equals 实现,但是使用了 getClass():if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; /*...*/ - Mick Mnemonic
使用Eclipse Luna。在空值检查后,生成的代码为getClass()!= obj.getClass() - Dakshinamurthy Karra
在生成.equals()方法时,使用instanceof还是getClass()的问题在是否有理由在生成.equals()方法时更喜欢使用getClass()而不是instanceof中得到了很好的解答。Josh Bloch更喜欢使用instanceof,他的论点可以从这篇(旧的)采访中找到。 - Mick Mnemonic
2个回答

4
它是不必要的,因为instanceof已经具有内置的null检查。 但是instanceof不仅仅是一个简单的foo == null。它是一条完整的指令,准备进行类检查,在进行null检查之前执行不必要的工作。(详见http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof
因此,单独使用null检查可能会提高性能。快速测量发现,没有惊喜的是,foo == null比使用instanceof进行null检查要快。
但通常在equals()中不会有大量的null值,这样就会剩下许多重复的不必要的null检查...很可能会耗尽任何在null比较期间实现的改进。
我的结论:这是不必要的。
用于测试的代码(记得使用-Djava.compiler=NONE,否则你只会测量Java的功率):
public class InstanceOfTest {
    public static void main(String[] args) {
        Object nullObject = null;

        long start = System.nanoTime();         
        for(int i = Integer.MAX_VALUE; i > 0; i--) {
            if (nullObject instanceof InstanceOfTest) {}
        }
        long timeused = System.nanoTime() - start;  

        long start2 = System.nanoTime();
        for(int i = Integer.MAX_VALUE; i > 0; i--) {
            if (nullObject == null) {}
        }
        long timeused2 = System.nanoTime() - start2;

        System.out.println("instanceof");
        System.out.println(timeused);       
        System.out.println("nullcheck");
        System.out.println(timeused2);
    }
}

1
我相信,在优化的环境中,instanceof指令在进行内部空值检查之前所执行的操作与手动进行的空值检查相比微不足道。 - Clashsoft
在优化环境中,我期望“手动”空值检查能够像内部的一样被优化。 (使用汇编语言,两者都将转化为MOV CMP JNE) - Sorcen
Instanceof作为一种使用堆栈的指令,其弹出(引用)和推入(结果)比简单的nullcheck更多。实际上会使用多少很难确定,可能因操作系统和硬件而异。(这就是为什么性能分析是检查性能问题的首选选项) - Sorcen

2

的确,这是不必要的,也是Eclipse模板作者的错误。而且这并不是第一个错误;我在那里发现了更多的小错误。例如,在我想省略null值时生成toString()方法:

public class A {
    private Integer a;
    private Integer b;

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("A [");
        if (a != null)
            builder.append("a=").append(a).append(", ");
        if (b != null)
            builder.append("b=").append(b);
        builder.append("]");
        return builder.toString();
    }
}

如果a不是nullb是,那么在关闭的]之前会有一个额外的逗号。
因此,关于您的声明:“现在,我猜测为Eclipse JDT编写代码模板的人也很专业。”,我认为他们确实很专业,但更加注意这些微小的不一致性也无妨。 :)

好的,现在我不确定默认值是什么(或者是什么),所以我从问题中编辑了那部分。 - Attila Csipak

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