equals 和 hashCode 的可重用实现
Reusable implementation of equals and hashCode
我正在做一个项目,其中许多 classes 需要 equals
和 hashCode
的适当典型实现:每个 class 都有一组初始化的最终字段在构造 "deeply" 不可变对象时(null
在某些情况下旨在被接受)用于散列和比较。
为了减少样板代码的数量,我考虑编写一个摘要 class 提供此类行为的通用实现。
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
}
这基本上是 here 的提议,但有一些差异。
在我看来,这似乎是一种减少冗长并且仍然有效实现 equals
和 hashCode
的简单而好的方法。但是,由于我不记得曾经见过类似的东西(除了上面链接的答案),我想在申请之前特别询问是否有反对这种方法的观点(或者可能有一些可以应用的改进)它贯穿整个项目。
我已经发现这种方法存在两个问题:
- 当且仅当它们的所有字段都匹配时,两个对象才被认为是相等的。在现实世界中情况并非总是如此。
- 通过将您的 classes 从
AbstractHashable
变成 extend
,您将无法再从任何其他 classes 变成 extend
。这是为重用 equals
和 hashCode
. 付出的相当高的代价
第一个问题可以通过将更多元数据传递给您的 AbstractHashable
class 来解决,这样它就可以识别哪些字段是可选的。例如,您可以将另一个数组传递给 AbstractHashTable
,其中包含要通过 setter 作为其元素忽略的索引位置。第二个问题可以通过使用 Composition 来解决。与其扩展 AbstractHashTable
,不如重构它,以便它可以与其用户建立 HAS-A 关系,而不是 IS-A关系。
However, as I don't recall to have ever seen something similar (except in the answer linked above), I would like to specifically ask whether is there some point against this approach
这种方法肯定会影响代码的可读性。如果您能想出一种更具可读性的方法(比如使用注释),我认为重用 equals
和 hashCode
实现没有任何问题。
综上所述,像 eclipse 这样的现代 IDE 可以很容易地为您生成 equals
和 hashCode
实现,所以真的有必要想出这种解决方案吗?我相信没有.
根据我的经验,这似乎很糟糕,因为您会增加在运行时与编译时可能遇到的错误(请记住,在列表等中使用这些对象现在会给您带来意想不到的行为)。如果 类 中的字段顺序不同怎么办?其次,你滥用继承? Maybee 选择了一些框架(https://projectlombok.org/),它根据注释生成哈希码和 equals?
该解决方案应该有效。
不过从OOP的角度感觉有点弱:
- 您正在复制数据(superclass 中的字段与对象中的字段),因此您的对象是原来的两倍大;另外,如果你需要让它们可变,你需要在两个地方维护状态
- 强制class层级,只提供equals/hashCode
现代 IDE 很容易生成这样的方法,如果你愿意,你也可以使用框架(如 Lombok)或库(如 Guava 的 Objects/Java 8 的对象)来简化这些方法。
我建议看一下 apache 的 HashCodeBuilder and EqualsBuilder 类。它也有基于反射的方法,如 reflectionHashCode 和 reflectionEquals。
我正在做一个项目,其中许多 classes 需要 equals
和 hashCode
的适当典型实现:每个 class 都有一组初始化的最终字段在构造 "deeply" 不可变对象时(null
在某些情况下旨在被接受)用于散列和比较。
为了减少样板代码的数量,我考虑编写一个摘要 class 提供此类行为的通用实现。
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
}
这基本上是 here 的提议,但有一些差异。
在我看来,这似乎是一种减少冗长并且仍然有效实现 equals
和 hashCode
的简单而好的方法。但是,由于我不记得曾经见过类似的东西(除了上面链接的答案),我想在申请之前特别询问是否有反对这种方法的观点(或者可能有一些可以应用的改进)它贯穿整个项目。
我已经发现这种方法存在两个问题:
- 当且仅当它们的所有字段都匹配时,两个对象才被认为是相等的。在现实世界中情况并非总是如此。
- 通过将您的 classes 从
AbstractHashable
变成extend
,您将无法再从任何其他 classes 变成extend
。这是为重用equals
和hashCode
. 付出的相当高的代价
第一个问题可以通过将更多元数据传递给您的 AbstractHashable
class 来解决,这样它就可以识别哪些字段是可选的。例如,您可以将另一个数组传递给 AbstractHashTable
,其中包含要通过 setter 作为其元素忽略的索引位置。第二个问题可以通过使用 Composition 来解决。与其扩展 AbstractHashTable
,不如重构它,以便它可以与其用户建立 HAS-A 关系,而不是 IS-A关系。
However, as I don't recall to have ever seen something similar (except in the answer linked above), I would like to specifically ask whether is there some point against this approach
这种方法肯定会影响代码的可读性。如果您能想出一种更具可读性的方法(比如使用注释),我认为重用 equals
和 hashCode
实现没有任何问题。
综上所述,像 eclipse 这样的现代 IDE 可以很容易地为您生成 equals
和 hashCode
实现,所以真的有必要想出这种解决方案吗?我相信没有.
根据我的经验,这似乎很糟糕,因为您会增加在运行时与编译时可能遇到的错误(请记住,在列表等中使用这些对象现在会给您带来意想不到的行为)。如果 类 中的字段顺序不同怎么办?其次,你滥用继承? Maybee 选择了一些框架(https://projectlombok.org/),它根据注释生成哈希码和 equals?
该解决方案应该有效。
不过从OOP的角度感觉有点弱:
- 您正在复制数据(superclass 中的字段与对象中的字段),因此您的对象是原来的两倍大;另外,如果你需要让它们可变,你需要在两个地方维护状态
- 强制class层级,只提供equals/hashCode
现代 IDE 很容易生成这样的方法,如果你愿意,你也可以使用框架(如 Lombok)或库(如 Guava 的 Objects/Java 8 的对象)来简化这些方法。
我建议看一下 apache 的 HashCodeBuilder and EqualsBuilder 类。它也有基于反射的方法,如 reflectionHashCode 和 reflectionEquals。