【问题标题】:An efficient equals(Object o) implementation一个高效的 equals(Object o) 实现
【发布时间】:2010-12-06 21:10:17
【问题描述】:

我在写完标题后阅读了这个 SO post,但仍然决定解决关于 Java 中 equals 的防错实现的问题。这是我的正常实现

@Override
        public boolean equals(Object o){
            if(o == null) return false;
            if(o instanceof CompositePk == false) return false;
            if(this == o) return true;
            CompositePk that = (CompositePk)o;
            return new EqualsBuilder().append(this.id, that.id)
                                      .append(this.bucketId, that.bucketId)
                                      .isEquals();
        }

使用 Apache 的 EqualsBuilder 来完成日常工作。比这更容易的是我的 Netbean 自动生成的 equals(o) 实现

 @Override
        public boolean equals(Object obj){
        if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final TemplatesWrapper other = (TemplatesWrapper) obj;
            if (this.timeAdded != other.timeAdded && (this.timeAdded == null || !this.timeAdded.equals(other.timeAdded))) {
                return false;
            }
            return true;
    }

我从 2 个差异项目中获取这些内容,但他们都试图完成相同的事情,但使用差异方法。您更喜欢哪种风格,或者您发现了哪些缺陷?

【问题讨论】:

    标签: java performance equals


    【解决方案1】:

    首先,无需测试null,然后测试instanceof,因为foo instanceof Barfoonull 时评估为false

    instanceof 运算符的结果与false 进行比较很奇怪,因为instanceof 是一个布尔运算。

    将课程与getClass() 进行比较充其量是有争议的。 Joshua Bloch,编写了大部分 Java 集合框架以及许多其他重要的东西,says

    这种技术(“getClass-based equals 方法") 确实满足等式 合同,但代价高昂。这 getClass 方法的缺点 是它违反了“Liskov 替代原则”,其中指出 (粗略地说)一种方法 期望一个超类实例必须 出现时行为正确 子类实例。如果子类添加 一些新方法,或者微不足道 修改行为​​(例如,通过发出 跟踪每个方法调用), 程序员会惊讶于 子类和超类实例 不能正常交互。对象 “应该相等”不会,导致 程序失败或行为 不规律地。问题是 由于 Java 的 集合基于equals 方法。

    您应该使用instanceof 而不是通过getClass() 进行比较,除非您有特定的技术原因不这样做。

    确定另一个对象与this 可比较后,您可以将原语与== 进行比较,将对象与equals 进行比较。如果您的任何成员对象都可以为空,那就更复杂了;然后,您必须编写详细的子句来比较可能为空的事物(或编写 bothNullOrEqual(Object a, Object b) 方法)。

    EqualsBuilder 方法在我看来是假的,但这只是一种“气味”,我不会在技术上反对。一般来说,我不喜欢在可能被频繁调用的方法中进行额外的方法调用。

    Apache 是假的,因为它测试 null 并使用 getClass() 比较。

    这是我的:

    @Override
    public boolean equals(final Object o) {
        if (!(o instanceof MyClass))
            return false;
        final MyClass om = (MyClass)o;
        // compare om's fields to mine
    }
    

    【讨论】:

    • EqualsBuilder,至少对于我拥有的版本而言,除非比较两个数组,否则不会进行 getClass 比较,这实际上是公平的游戏(在大多数情况下)。
    • 决赛的目的是什么?你害怕你可能会重新分配 om?
    • final 有助于推理。当您进一步阅读时,要考虑的事情就更少了。我希望 Java 中的所有内容都是 final,除非标记为 mutable
    • 链接到 equals 上的 Bloch(有效 Java 第一版第 3 章):java.sun.com/developer/Books/effectivejava/Chapter3.pdf
    【解决方案2】:

    我会这样做:

    public boolean equals(Object ob) {
      if (ob == null) return false;
      if (ob == this) return true;
    
      if (!(ob instanceof MyClass)) return false; // OR
      if (ob.getClass() != getClass()) return false;
    
      // check relevant members
    }
    

    中间的两条线不同。一个允许子类相等(第一个),另一个不允许。使用任何一个合适的。

    举个例子,Java 的AbstractList 类可能会使用第二种形式,因为List 的确切实现是无关紧要的。重要的是成员是否平等且处于相同的位置。

    相反,Person 类应该使用第一种形式(instanceof),因为如果有 Student 子类并且您调用 Person.equals(Student),它可能会在不检查 Student 中的额外字段的情况下返回 true,而 Student.equals(Person) 可能会返回false。如果equals() 不是可交换的,那你就是在自找麻烦。

    我倾向于使用由我的 IDE (IntelliJ IDEA) 生成的 equals() 方法,而不是创建对某些 Apache 库的不必要的依赖以获取少量收益。

    【讨论】:

    • 出于这个原因,我更喜欢使用 Apache Commons EqualsBuilder:添加新字段时使用它的维护更少。但是,您可以争辩说,每次添加字段时,您只需让 IDEA 重新生成 equals,这也是一个公平的观点。各有各的。 :-P
    • 您知道|| 运算符吗? ;-)
    【解决方案3】:

    Apache 的比你的或 cletus 的好。

    就我模糊的记忆而言,在 equals 中使用 instanceof 存在问题;我还不能完全解释为什么,也许有人会详细说明。我可能是错的。

    -- 编辑:

    正如ChrisSteve 下面的有用解释,我在想equals 实现的“symmetric property”。在此基础上,我可以支持我更喜欢 Apache 实现的主张:)

    【讨论】:

    • 使用instanceof 的麻烦在于,如果您将一个 A 类对象与 B 类对象(其中 B 是 A 的子类)进行比较,那么它是不对称的。但是要进行“类相等”比较是偶数更差;那么子类永远不会被认为与超类实例相等,即使可替换性表明它们应该相等。即,instanceof 是两害相权取其轻。
    • 使用 instanceof 违反了 equals 的对称属性。也就是说,当且仅当 y.equals(y) 为真时 x.equals(y) 为真。这是比较类(使用 getClass)在技术上是正确的。
    • 史蒂夫:是的,就是这样。谢谢。很高兴我因为提出这个问题而被否决了,但无论如何,你已经解释过了,所以一切都很好:)
    • @silky:我先提出来你不承认我?就是这样,再吃一个-1。 (开个玩笑;不要为此浪费我的代表点。):-P
    • 克里斯:你的回答太罗嗦了,把我可怜的脑子给炸了,二读时你们都承认了!为所有人点赞!
    【解决方案4】:

    老实说,你必须编写的代码越少,你就越好(在大多数情况下)。

    生成的代码已被许多人调试和使用。您不妨使用生成的内容(如果您需要提高性能,请这样做)。

    使用生成的代码的优势:只要您的实例字段发生更改(并且此生成的代码没有被修改),您就可以简单地重新生成代码。

    有时,考虑可维护性更容易。经验法则:您自己编写的代码越少,您需要调试的代码就越少。如果生成的代码不会对性能造成巨大影响,那就生成吧!

    【讨论】:

      【解决方案5】:

      解释:当覆盖equals方法时,hashCode()方法也必须被覆盖。因此,考虑如下所示的具有 3 个属性的类,并考虑到所有属性对相等性都很重要,equals() 实现必须测试所有这些字段。条件的顺序并不重要,但必须对所有字段进行相等性测试才能完全考虑对象之间的相等性。

      public class SampleClass {
      
        private Long id;
        private String description;
        private Date creation;
      
          @Override
          public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + ((creation == null) ? 0 : creation.hashCode());
              result = prime * result + ((description == null) ? 0 : description.hashCode());
              result = prime * result + ((id == null) ? 0 : id.hashCode());
              return result;
          }
      
          @Override
          public boolean equals(Object obj) {
              boolean isEquals = true;
              if (this == obj) { isEquals = true; }
              else if (obj == null) { isEquals = false; } 
              else if (getClass() != obj.getClass()) { isEquals = false;  }
              else { 
                  SampleClass other = (SampleClass) obj;
                  if (creation == null) {
                      if (other.creation != null) isEquals = false;
                  } else if (!creation.equals(other.creation)) {
                      isEquals = false;
                  } else if (description == null) {
                      if (other.description != null) isEquals = false;
                  } else if (!description.equals(other.description)) {
                      isEquals = false;
                  } else  if (id == null) {
                      if (other.id != null) isEquals = false;
                  } else if (!id.equals(other.id)) {
                      isEquals = false;
                  }
              }
              return isEquals;
          }
      

      【讨论】:

      • 考虑在答案中添加一些解释。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-22
      • 1970-01-01
      • 1970-01-01
      • 2011-11-05
      • 2018-01-23
      相关资源
      最近更新 更多