【问题标题】:Check if two objects are completely equal in Java检查两个对象在Java中是否完全相等
【发布时间】:2015-10-13 13:39:01
【问题描述】:

我有一个 Java 类,这里有一个例子:

public class Car {

    private int fuelType;
    private Date made;
    private String name;
.
.
. // and so on

现在假设我有两个汽车对象,我想比较它们的所有变量是否相等。

现在,我通过覆盖方法 equals(Object o) 解决了这个问题,我检查了两个对象中的所有变量是否匹配。

这里的问题是,如果我有 20 个类,我将不得不在每个类中覆盖 equals(Object o)

有没有办法创建某种通用方法,可以比较我传递给它的两个对象中的任何一个,并让我知道它们是否在每个变量中匹配?

【问题讨论】:

  • 如果使用 Eclipse 右键单击​​ -> 源 -> 生成等于和哈希。生成的等号将使用您设置的任何字段进行比较。顺便说一句,没错,就是std方法Object.equals(Object other)
  • 是的。您可以创建一个使用一些反射的助手,并最终得到只是绑定的脆弱代码,以便在某些时候产生问题。或者你可以把它吸一遍,然后通过你的 IDE 提供的 20 代窗口工作。
  • 这是最简单的方法,也可以使用反射:stackoverflow.com/questions/1449001/…

标签: java


【解决方案1】:

您有几个自动化 Equals & Hashcode 的选项(选项 #3 让我大吃一惊!):

  1. 您的 IDE。对于大多数对象,我不会推荐它,因为它们可能会随着实际的类定义慢慢过时。它们看起来也很丑陋,并且会使用样板代码污染您的代码库。
  2. Apache Commons 有很多东西可以让这更容易,包括reflective version,所以没有与类定义过时的风险。它比 #1 好,除非您需要快速的 equals/hashcode,但我喜欢的样板仍然太多。
  3. Project Lombok 和注解处理。在 ya 类上打一个 EqualsAndHashCode 注释并完成它。 我推荐使用 Project Lombok。它为构建增加了一点魔力(但不多),因此需要一个插件来让您的 IDE 表现良好,但对于没有样板代码来说,这是一个很小的代价。 Lombok 是一个在编译时运行的注释处理器,因此不会影响运行时性能。
  4. 使用开箱即用的支持它的不同语言,但也以 JVM 为目标。 Groovy 使用 annotationKotlin 支持 data classes。除非您现有的代码可以快速转换,否则我会避免这种情况。
  5. Google's Auto 有一个 AutoValue。与 Project Lombok 一样,这是一个注解处理器,但它的魔力更少,但以更少的样板为代价(感谢 Louis Wasserman

【讨论】:

  • 另外,如果你不喜欢魔法生成的方法,github.com/google/auto/tree/master/value 可以用更少的魔法实现与 Lombok 类似的目标。 This section 讨论了与 Lombok 的一些差异。
  • 根据我的经验(在一些相对较大的 250k+ LOC Java 应用程序上工作)反射往往会使代码更难理解和维护,在引入它之前应该仔细考虑(忽略可能的性能问题,这很容易无论如何,稍后将在此逐案解决)。由于在更改数据函数的状态时忘记调整 equals 方法,我不记得有一个错误。此类数据类往往相当短且简单,因此至少在代码审查中很容易发现这一点。
  • 感谢您的详细解答。我现在选择了#1,但我真的很喜欢你说的#3。 #1 现在就足够了,但是如果我遇到在类更改时生成新代码很麻烦的地步,我将只使用 #3。
  • @Voo - 我参与过大大小小的项目。我肯定遇到过由于忘记更新 equals 方法而引入的错误(即使有代码审查)。虽然总的来说我同意反射增加了不必要的魔法,但当很好地包裹在一个 API 中时,我个人对此没有任何问题。
  • @mlk 我不想让人觉得总是反对反思(肯定有额外的心理开销值得付出代价的用例),我只是变得更加谨慎多年来 - 糟糕的经历。我同意它是无害的,因为它被很好地包裹起来,而且这里是一个纯粹的实现细节。所以在反省时,我可能在这里过于谨慎了。
【解决方案2】:

你可以使用:

 org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare(Object lhs, Object rhs);

它使用反射来比较文件 这是javadoc:javadoc

【讨论】:

【解决方案3】:

我将在这里接受大多数人的不同意见(使用带有反射的 apache commons):是的,这是您必须编写的一些代码(让您的 IDE 真正生成),但您只需执行一次并且需要实现 equals/hashcode 的数据类的数量通常相当易于管理 - 至少在我从事的所有大型项目 (250k+ LOC) 中是这样。

当然,如果您向该类添加新成员,您必须记住更新 equals/hashcode 函数,但这通常很容易注意到,最迟在代码审查期间。

老实说,如果您使用 Java7 中的简单小帮助程序类,您可以减少 Wana Ant 大量展示的代码。你真正需要的是:

@Override
public boolean equals(Object o) {
    if (o instanceof Car) { // nb: broken if car is not final - other topic
        Car other = (Car) o;
        return Objects.equals(fuelType, other.fuelType) && 
               Objects.equals(made, other.made) && 
               Objects.equals(name, other.name);
    }
    return false;
}

hashcode 类似:

@Override
public int hashCode() {
    return Objects.hash(fuelType, made, name);
}

不如反射解决方案那么短?没错,但它很简单,易于维护、适应和阅读 - 性能要好几个数量级(这对于实现 equals 和 hashcode 的类通常很重要)

【讨论】:

    【解决方案4】:

    通常,您可以通过 IDE 生成 equals/hashCode 方法 - 该领域的所有大玩家都能够做到这一点(Eclipse、IntelliJ Idea 和 Netbeans)。

    通常您可以创建一些将使用反射的代码,但我不推荐使用这种代码,因为客观方法更清晰且更易于维护。反射也不会像“标准”方式那样快。如果你真的想走这条路,有 EqualsBuilderHashCodeBuilder 这样的实用程序。

    仅供参考,有一些基于 JVM 的语言已经支持这些功能,例如Kotlin data classes,可以很好地用于现有的 Java 项目。

    【讨论】:

      【解决方案5】:

      我将插入一个我最喜欢的解决此问题的方法的插件:@AutoValue

      这是一个来自 Google 的开源项目,它提供了一个注释处理器,可以生成一个合成类,为您实现 equalshashCode

      由于它是自动生成的代码,因此您不必担心意外忘记字段或弄乱equalshashCode 实现。但由于代码是在编译时生成的,因此运行时开销为零(与基于反射的解决方案不同)。它也是“API 不可见的”——你的类的用户无法区分 @AutoValue 类型和你自己实现的类型,你可以在未来来回更改而不会破坏调用者。

      另请参阅 this presentation,它解释了基本原理并将其与其他方法进行比较。

      【讨论】:

        【解决方案6】:

        理论上,您可以使用反射来创建某种实用程序,正如许多人在 cmets 中建议的那样。我个人不建议你这样做。你最终会得到部分工作的东西。

        Java 中的许多东西都依赖于equalhashCode,例如方法contains,您可以在任何实现Collection 的东西中找到它。

        推荐的解决方案是覆盖equal(和hashCode)。此外,我认为任何体面的 IDE 都可以选择为您生成它们。因此,您可以比使用反射更快地做到这一点。

        【讨论】:

          【解决方案7】:

          我会这样做:

          @Override
          public boolean equals(Object obj) {
              if (obj instanceof Car) {
                  return internalEquals((Car) obj);
              }
              return super.equals(obj);
          }
          
          protected boolean internalEquals(Car other) {
              if(this==other){
                  return true;
              }
              if (other != null) {
                  //suppose fuelType can be Integer.
                  if (this.getFuelType() !=null) {
                      if (other.getFuelType() == null) {
                          return false;
                      } else if (!this.getFuelType().equals(other.getFuelType())) {
                          return false;
                      }
                  } else if(other.getFuelType()!=null){
                      return false;
                  }
                  if (this.getName() != null) {
                      if (other.getName() == null) {
                          return false;
                      } else if (!this.getName().equals(other.getName())) {
                          return false;
                      }
                  }
                  else if(other.getName()!=null){
                      return false;
                  }
                   if (this.getDate() != null) {
                      if (other.getDate() == null) {
                          return false;
                      } else if (!this.getDate().getTime()!=(other.getDate().getTime())) {
                          return false;
                      }
                  }
                  else if(other.getDate()!=null){
                      return false;
                  }
                  return true;
              } else {
                  return false;
              }
          }
          

          编辑
          简化版

               public class Utils{
                    /**
                     * Compares the two given objects and returns true, 
                     * if they are equal and false, if they are not.
                     * @param a one of the two objects to compare
                     * @param b the other one of the two objects to compare
                     * @return if the two given lists are equal.
                     */
                     public static boolean areObjectsEqual(Object a, Object b) {
          
                         if (a == b){
                            return true;
                         }
                         return (a!=null && a.equals(b));
                    }
          
                    public static boolean areDatesEqual(Date a, Date b){
                       if(a == b){
                          return true;
                       }
                       if(a==null || b==null){ 
                          return false;
                       }
                       return a.getTime() == b.getTime();
                    }
             }
          
             @Override
             public boolean equals(other obj) {
                if(this == other){
                   return true;
                } 
                if(other == null){
                    return false;
                }
                if (other instanceof Car) {
                    return internalEquals((Car) other);
                }
                return super.equals(obj);
             }
          
             protected boolean internalEquals(Car other) {        
                  //suppose fuelType can be Integer.
                  if (!Utils.areObjectsEqual(this.getName(), other.getName()){                   
                      return false;
                  }
                  if (!Utils.areObjectsEqual(this.getName(), other.getName()){ 
                      return false;
                  }
                  if (!Utils.areDatesEqual(this.getDate(), other.getDate()){ 
                      return false;
                  } 
                  return true;
              }
          }
          

          也不要忘记哈希码,它们是携手编码的。

          【讨论】:

          • 首先 equals 必须处理以 null 作为参数调用,因此一半的 null 检查是不必要的。其次,我推荐一个简单的 equalsHelper 类来处理自己的属性可以为 null 的情况,从而大大简化代码。它只不过是o1 == o2 || (o1 != null && o1.equals(o2) 并大大简化了您的代码。编辑:它甚至在 Java7 see here 中也是默认的。
          • 是的,这就是我在我的应用程序(实用程序类)中所拥有的,但他想要一些平等的东西。此外,关于 null,在 equals 的开头也许您可以处理 null 对象检查。至于其他的,我知道 null 部分,我只是想让他看看它是如何工作的。
          • 老实说,与实际需要的代码相比,大量的代码可能只会把他吓跑——如果我不得不写这样的东西肯定会吓到我。
          • 这取决于经验,一开始你必须 - 我的意见,理解洞察力,接下来,你可以简化。或者至少那是我的道路。
          猜你喜欢
          • 2023-03-07
          • 2019-11-04
          • 1970-01-01
          • 2016-10-22
          • 1970-01-01
          • 2016-06-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多