equals和hashCode的可重用实现



我正在从事一个项目,其中许多类需要equalshashCode的适当典型实现:每个类都有一组在构造时初始化的最终字段,这些字段使用"深度"不可变对象(null在某些情况下是可接受的)用于哈希和比较。

为了减少样板代码的数量,我考虑编写一个抽象类,提供此类行为的常见实现。

public abstract class AbstractHashable {
/** List of fields used for comparison. */
private final Object[] fields;
/** Precomputed hash. */
private final int hash;
/**
* Constructor to be invoked by subclasses.
* @param fields list of fields used for comparison between objects of this
* class, they must be in constant number for each class
*/
protected AbstractHashable(Object... fields) {
this.fields = fields;
hash = 31 * getClass().hashCode() + Objects.hash(fields);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
AbstractHashable other = (AbstractHashable) obj;
if (fields.length != other.fields.length) {
throw new UnsupportedOperationException(
"objects of same class must have the same number of fields");
}
for (int i=0; i<fields.length; i++) {
if (!fields[i].equals(other.fields[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hash;
}
}

这是这样使用的:

public class SomeObject extends AbstractHashable {
// both Foo and Bar have no mutable state
private final Foo foo;
private final Bar bar;
public SomeObject(Foo foo, Bar bar) {
super(foo, bar);
this.foo = Objects.requireNonNull(foo);
this.bar = bar; // null supported
}
// other methods, no equals or hashCode needed
}

这基本上就是这里提出的,但有一些不同。

在我看来,这是一种简单而好的方法,可以减少冗长,并且仍然可以有效地实现equalshashCode。然而,由于我不记得见过类似的东西(除了上面链接的答案),在将其应用于整个项目之前,我想特别问一下,这种方法是否有一些反对之处(或者可能有一些可以应用的改进)。

我已经看到这种方法存在两个问题:

  1. 两个对象将被视为相等,当且仅当它们的所有字段匹配时。现实世界中并不总是这样
  2. 通过将AbstractHashable中的类设为extend,您就不能再从任何其他类中创建extend。这对于重用equalshashCode来说是相当高的代价

第一个问题可以通过向AbstractHashable类传递更多元数据来解决,该类允许它识别哪些字段是可选的。例如,您可以通过setter将另一个数组传递给AbstractHashTable,该数组包含要作为其元素忽略的索引位置。第二个问题可以通过使用Composition来解决。与其扩展AbstractHashTable,不如对其进行重构,使其能够与其用户建立HAS-a关系,而不是IS-a

然而,由于我不记得见过类似的东西(除了上面链接的答案),我想特别问一下,是否有反对这种方法的理由

这种方法肯定会影响代码的可读性。如果您能想出一种可读性更强的方法(比如使用注释),那么我认为重用equalshashCode实现没有任何问题。

话虽如此,像eclipse这样的现代IDE可以很容易地为您生成equalshashCode实现,所以真的需要想出这样的解决方案吗?我相信没有

根据我的经验,这似乎很糟糕,因为与编译时相比,运行时可能会增加错误(记住列表等中所有这些对象的使用现在都会给你带来意想不到的行为)。如果类中字段的顺序不同怎么办?其次,你是否滥用了继承权?Maybee选择了一些框架(https://projectlombok.org/)哪个基于注释生成hashcode和equals?

解决方案应该可以工作。

然而,从OOP的角度来看,它感觉有点弱:

  • 您正在复制数据(超类中的字段与对象中的字段),因此您的对象是原来的两倍大;此外,如果您需要使它们可变,则需要在两个位置维护状态
  • 类层次结构是强制的,只提供equals/hashCode

现代IDE很容易生成这样的方法,如果你愿意,你也可以使用框架(如Lombok)或库(如Guava的Objects/Java 8的Objects)来简化这些方法。

我建议看一下apache的HashCodeBuilder和EqualsBuilder类。它有基于反射的方法,如reflectionHashCode和reflectionEquals。

相关内容

  • 没有找到相关文章

最新更新