14

我们有一个 OO 代码库,在很多情况下hashcode()根本equals()不起作用,主要是因为以下原因:

除非您愿意放弃面向对象抽象的好处,否则无法在保留 equals 契约的同时扩展可实例化类并添加值组件。

这是 Joshua Bloch 的“Effective Java”中的一句话,在 Artima 的一篇很棒的文章中有更多关于这个主题的内容:

http://www.artima.com/lejava/articles/equality.html

我们对此非常满意,这不是这个问题的意义所在。

问题是:看到在某些情况下您无法满足equals()合同是事实,那么自动生成hashcode()equals()抛出 UnsupportedOperationException 的干净方法是什么?

注释会起作用吗?我正在考虑类似的事情@NotNull:每次@NotNull违反合同都会自动引发异常,除了用@NotNull.

这很方便,因为它是 8 个字符(“@NotNull”),而不是不断重复相同的验证/抛出异常代码。

在我担心的情况下,在每个hashCode()/equals()没有意义的实现中,我们总是重复同样的事情:

@Override
public int hashCode() {
    throw new UnsupportedOperationException( "contract violation: calling hashCode() on such an object makes no sense" );
}

@Override
public boolean equals( Object o ) {
    throw new UnsupportedOperationException( "contract violation: calling equals() on such an object makes no sense" );
}

然而,这很容易出错:我们可能会错误地忘记剪切/粘贴它,这可能会导致用户误用这些对象(比如试图将它们放入默认的 Java 集合中)。

或者如果不能通过注释来创建这种行为,AOP 会起作用吗?

有趣的是,真正的问题是 Java 层次结构的存在hashCode()equals()顶部,这在某些情况下根本没有意义。但是那么我们如何干净地处理这个问题呢?

4

4 回答 4

10

我同意你对这是一个问题的评估,hashCode首先equals是在 Object 中定义的。长期以来,我一直认为应该以与排序相同的方式处理相等性 - 一个界面说“我可以与 X 的实例进行比较”,另一个说“我可以比较 X 的两个实例”。

另一方面,这实际上是否给您带来了任何错误?人们是否一直在尝试使用equals以及hashCode不应该使用的地方?因为即使您可以使代码库中的每个类在恰当地调用这些方法时都抛出异常,但对于您正在使用的其他类(无论是来自 JDK 还是第三方库)而言,情况也并非如此。

我相信您可以使用某种形式的 AOP 来做到这一点,无论是正常的注释处理还是其他 - 但您有证据表明奖励值得付出努力吗?

另一种看待它的方式:这在您扩展另一个已经覆盖hashCodeand的类的情况下equals,对吗?否则,您可以使用 Object 的 hashCode/equals 方法的“平等 = 身份”性质,这仍然很有用。你有很多属于这一类的课程吗?hashCode你能不能只写一个单元测试来通过反射找到所有这些类型,并在你调用/时检查这些类型是否抛出异常equals?(如果他们有一个无参数的构造函数,这可以是自动化的,或者有一个已经检查过的类型的手动列表 - 如果有一个不在“已知良好”列表中的新类型,单元测试可能会失败。)

于 2010-02-05T07:19:49.423 回答
6

我不明白你为什么认为“在某些情况下你不能满足 equals() 合同”?相等的含义由类定义。因此,使用 Object 的 equal 是完全有效的。如果您没有覆盖equals,那么您将每个实例定义为唯一的。

似乎有一种误解,认为 equals 是始终需要覆盖的方法之一,并且它必须检查其所有字段。我会反对——除非你对相等的定义不同,否则不要覆盖 equals。

我也不同意 artima 的文章,特别是“陷阱 #3:根据可变字段定义 equals”。一个类基于可变字段定义其相等性是完全有效的。用户在将它与集合一起使用时要注意这一点。如果一个可变对象在其可变状态上定义了它的相等性,那么不要期望两个实例在一个实例发生变化后相等。

我认为抛出 UnsupportedOperation 违反了 equals 的冲刺。对象的等于状态:

Object 类的 equals 方法实现了对象上最有区别的可能等价关系;也就是说,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象(x == y 的值为 true)时,此方法才返回 true。

因此,我应该能够调用 equals 并根据 Object 的 equals 定义或覆盖的 equals 定义获得一个真值或假值。

于 2010-02-05T08:13:45.977 回答
4

为什么不让您的 IDE(Eclipse/NetBeans/IntelliJ)为您生成hashCode()equals()方法。他们在这方面做得很好。

当然,AOP 会起作用,但它相当复杂。这意味着您将无法在几乎没有集合或实用程序的情况下使用这些对象。

另一个合乎逻辑的解决方案是仅删除那些不起作用的方法的实现,从而有效地将实现仅保留在Object.

于 2010-02-05T07:20:41.933 回答
1

在 Java 或 .NET 中的所有对象之间至少可以定义两种等价关系:

  • 如果用对 Y 的引用覆盖 X 不会改变 X 或 Y 的任何成员的当前或未来行为,则两个对象引用 X 和 Y 是完全等价的。

  • 两个对象引用 X 和 Y 具有等效的状态,如果在一个没有持久化从身份相关哈希函数返回的值的程序中,将所有对 X 的引用与对 Y 的所有引用交换将使程序状态保持不变。

我有一个X指向 FordBlazer 的参考 ( )。我有另一个 ( Y) 指向 SiameseCat。它们是等价的吗?不,他们不是,所以X.equals(Y)应该是错误的。对象的类型彼此之间没有关系这一事实不是问题——如果有的话,它会使事情变得更容易(如果唯一可以等同于 SiameseCat 的东西是另一个 SiameseCat,事实Y并非如此SiameseCat意味着X.equals()不需要检查其他任何东西。

虽然对于特定对象是否应该实现等价的第一个或第二个定义可能存在一些争论,但值得注意的是,任何将equals报告不同对象定义为不相等的对象,无论其状态的任何其他方面如何,都将与自身一致(如果 X .Equals(X) 与 X.Equals(Y) 不匹配,这意味着 Y 的行为与 X 不同)。因此,如果对 没有更好的处理equals,则继承自的默认定义object是一个非常好的和有意义的定义。

唯一hashCode可能遇到问题的情况是,如果代码可能(不明智地)在对象存储在 HashTable 中时改变对象的某些方面。对此的适当补救措施是hashCode不依赖于对象状态的任何可变方面。如果一个对象的状态除了它的类之外没有有意义的不可变方面,只需为该类组成一个任意数字并hashCode始终返回它。大型哈希表对此类对象的性能较差,但小型哈希码可以正常工作。不能为一个类型定义一个好的散列码这一事实不应该阻止它在包含十几个项目的 HashTable 中使用。

于 2013-08-04T17:19:15.903 回答