我应该重写 Collections 的 hashCode() 吗?
Should I override hashCode() of Collections?
考虑到我 class 里面有各种字段:
class MyClass {
private String s;
private MySecondClass c;
private Collection<someInterface> coll;
// ...
@Override public int hashCode() {
// ????
}
}
其中,我确实有各种想要存储在 HashMap
中的对象。为此,我需要 MyClass
.
的 hashCode()
我将不得不进入所有字段和各自的父 classes 递归 以确保它们都实现 hashCode()
正确,因为否则 MyClass
的 hashCode()
可能不会考虑某些值。这样对吗?
我可以用那个 Collection
做什么?我可以一直依赖它的 hashCode()
方法吗?它会考虑我的 someInterface
对象中可能存在的所有子值吗?
我在这里打开了关于唯一标识对象的实际问题的第二个问题:
澄清:
is there anything more or less unqiue in your class? The String s? Then only use that as hashcode.
如果其中一个对象的 coll
中的任何值发生更改,则两个对象的 MyClass hashCode() 肯定会有所不同。如果两个对象的所有字段递归地存储相同的值,HashCode 应该只 return 相同的值。基本上,在 MyClass 对象上进行一些耗时的计算。如果计算已经在一段时间前使用完全相同的值完成,我想节省这次时间。为此,如果结果已经可用,我想在 HashMap 中查找。
Would you be using MyClass in a HashMap as the key or as the value? If the key, you have to override both equals() and hashCode()
因此,我使用 MyClass 的 hashCode 作为 HashMap 中的 key。值(计算结果)将有所不同,例如 Integer(简化)。
What do you think equality should mean for multiple collections? Should it depend on element ordering? Should it only depend on the absolute elements that are present?
这不会取决于存储在 coll
中的集合类型吗?虽然我猜顺序不是很重要,不
您从该站点获得的响应非常棒。谢谢大家
@AlexWien that depends on whether that collection's items are part of the class's definition of equivalence or not.
是的,是的。
- I'll have to go into all fields and respective parent classes recursively to make sure they all implement
hashCode()
properly, because otherwise hashCode()
of MyClass
might not take into consideration some values. Is this right?
没错。它并不像听起来那么繁琐,因为经验法则是如果您覆盖 equals()
,则只需要覆盖 hashCode()
。您不必担心使用默认 equals()
的 class;默认的 hashCode()
就足够了。
此外,对于您的 class,您只需要散列您在 equals()
方法中比较的字段。例如,如果这些字段之一是唯一标识符,您只需在 equals()
中检查该字段并将其散列在 hashCode()
.
中即可。
所有这一切都取决于您是否也压倒了 equals()
。如果你没有覆盖它,也不要打扰 hashCode()
。
- What do I do with that
Collection
? Can I always rely on its hashCode()
method? Will it take into consideration all child values that might exist in my someInterface
object?
是的,您可以依靠 Java 标准库中的任何集合类型来正确实现 hashCode()
。是的,任何 List
或 Set
都会考虑其内容(它将项目的哈希码混合在一起)。
根据您的说明:
您想将 MyClass
作为键存储在 HashMap
中。
这意味着 hashCode
() 在添加对象后不允许更改。
因此,如果您的集合在对象实例化后可能会发生变化,则它们不应成为 hashcode() 的一部分。
来自http://docs.oracle.com/javase/8/docs/api/java/util/Map.html
Note: great care must be exercised if mutable objects are used as map
keys. The behavior of a map is not specified if the value of an object
is changed in a manner that affects equals comparisons while the
object is a key in the map.
对于 20-100 个对象,不值得冒 hash() 或 equals() 实现不一致的风险。
在您的情况下无需重写 hahsCode() 和 equals()。
如果您不覆盖它,java 会采用 equals 和 hashcode() 的唯一对象标识(这很有效,特别是因为您声明考虑到对象字段的值不需要 equals() ).
使用默认实现时,您是安全的。
当哈希码在插入后更改时,在 HashMap 中使用自定义 hashcode() 作为键等错误,因为您使用集合的 hashcode() 作为对象哈希码的一部分可能会导致极难找到错误。
如果需要查重计算是否完成,我不会absue equals()。只需编写自己的方法 objectStateValue()
并在集合上调用 hashcode() 即可。这不会干扰对象 hashcode 和 equals()。
public int objectStateValue() {
// TODO make sure the fields are not null;
return 31 * s.hashCode() + coll.hashCode();
}
另一种更简单的可能性:执行耗时计算的代码可以在计算准备就绪后立即将 calculationCounter 增加 1。然后您只需检查计数器是否已更改。这样更便宜也更简单。
所以您想对对象的内容进行计算,这将为您提供一个唯一的键,您将能够在 HashMap
中检查 "heavy" 计算是否是您 不要 已针对给定的深度字段组合执行两次操作。
单独使用hashCode
:
我认为 hashCode
不适合用于您所描述的场景。
hashCode
应该 总是 与 equals()
结合使用。这是它的契约的一部分,而且是一个重要的部分,因为 hashCode()
return 是一个整数,尽管人们可能会尝试使 hashCode()
尽可能均匀分布,但它不会对于相同 class 的每个可能对象都是唯一的,除了非常特殊的情况(例如 Integer
、Byte
和 Character
很容易...)。
如果您想亲自查看,请尝试生成最多 4 个字母(小写和大写)的字符串,并查看其中有多少具有相同的哈希码。
HashMap
因此在查找散列 table 中的内容时同时使用 hashCode()
和 equals()
方法。将有具有相同 hashCode()
的元素,您只能通过使用 equals()
对您的 class.
测试所有元素来判断它们是否相同
同时使用 hashCode
和 equals
在这种方法中,您使用对象本身作为哈希映射中的键,并为其提供适当的 equals
方法。
要实施equals
方法,您需要深入到您的所有领域。为了你的大计算,他们所有的 classes 必须有 equals()
匹配你认为相等的东西。当您的对象实现接口时需要特别小心。如果计算是基于对该接口的调用,并且实现该接口的不同对象 return 在这些调用中具有相同的值,那么它们应该以反映该接口的方式实现 equals
。
并且它们的 hashCode
应该与 equals
匹配 - 当值相等时,hashCode
必须相等。
然后您根据所有这些项目构建您的 equals
和 hashCode
。您可以使用 Objects.equals(Object, Object)
和 Objects.hashCode( Object...)
来节省大量样板代码。
但这是一个好方法吗?
虽然您可以在对象中缓存 hashCode()
的结果并在不对其进行变异的情况下重新使用它,但您不能对 equals
执行此操作。这意味着 equals
的计算将会很长。
因此,根据每个对象调用 equals()
方法的次数,这种情况会加剧。
例如,如果 hashMap
中有 30 个对象,但将出现 300,000 个对象并与它们进行比较时才意识到它们与它们相等,你'将进行 300,000 次重比较。
如果您只有很少的实例,其中一个对象将具有相同的 hashCode
或属于 HashMap
中的同一个桶,需要比较,然后继续equals()
方法可能会很有效。
如果你决定走这条路,你需要记住:
如果对象是 HashMap
中的键,只要它存在,就应该不 被改变。如果你需要改变它,你可能需要对它进行深拷贝并将副本保存在哈希映射中。又是深拷贝,需要考虑里面所有的对象和接口,看是否完全可拷贝。
为每个对象创建唯一键
回到您最初的想法,我们已经确定 hashCode
不是散列映射中键的良好候选者。一个更好的候选者是散列函数,例如 md5
或 sha1
(或更高级的散列,如 sha256,但在您的情况下不需要加密强度),其中冲突很多比 int
更稀有。您可以将 class 中的所有值,将它们转换为字节数组,使用这样的哈希函数对其进行哈希处理,并将其十六进制字符串值作为您的映射键。
这自然不是小菜一碟。所以你需要考虑它是否真的为你试图避免的计算节省了很多时间。它可能比重复调用 equals()
来比较对象更快,因为每个实例只执行一次,与它在 "big calculation".
时的值进行比较
对于给定的实例,您可以缓存结果并且不再计算它,除非您改变对象。或者您可以仅在执行 "big calculation".
之前再次计算它
但是,您需要 class 中所有对象的 "cooperation"。也就是说,它们都需要合理地转换为字节数组,以使两个等效对象产生相同的字节(包括我上面提到的接口对象的相同问题)。
你还应该注意你有两个字符串 "AB" 和 "CD" 的情况,它们会给你与 "A" 和 [=115= 相同的结果],然后您将得到两个不同对象的相同散列。
对于未来的读者。
是的,equals 和 hashCode 齐头并进。
下面显示了使用辅助库的典型实现,但它确实显示了 "hand in hand" 的性质。来自 apache 的帮助程序库使事情变得更简单恕我直言:
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyCustomObject castInput = (MyCustomObject) o;
boolean returnValue = new org.apache.commons.lang3.builder.EqualsBuilder()
.append(this.getPropertyOne(), castInput.getPropertyOne())
.append(this.getPropertyTwo(), castInput.getPropertyTwo())
.append(this.getPropertyThree(), castInput.getPropertyThree())
.append(this.getPropertyN(), castInput.getPropertyN())
.isEquals();
return returnValue;
}
@Override
public int hashCode() {
return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
.append(this.getPropertyOne())
.append(this.getPropertyTwo())
.append(this.getPropertyThree())
.append(this.getPropertyN())
.toHashCode();
}
17、37 .. 您可以选择自己的值。
考虑到我 class 里面有各种字段:
class MyClass {
private String s;
private MySecondClass c;
private Collection<someInterface> coll;
// ...
@Override public int hashCode() {
// ????
}
}
其中,我确实有各种想要存储在 HashMap
中的对象。为此,我需要 MyClass
.
hashCode()
我将不得不进入所有字段和各自的父 classes 递归 以确保它们都实现
hashCode()
正确,因为否则MyClass
的hashCode()
可能不会考虑某些值。这样对吗?我可以用那个
Collection
做什么?我可以一直依赖它的hashCode()
方法吗?它会考虑我的someInterface
对象中可能存在的所有子值吗?
我在这里打开了关于唯一标识对象的实际问题的第二个问题:
澄清:
如果其中一个对象的is there anything more or less unqiue in your class? The String s? Then only use that as hashcode.
coll
中的任何值发生更改,则两个对象的 MyClass hashCode() 肯定会有所不同。如果两个对象的所有字段递归地存储相同的值,HashCode 应该只 return 相同的值。基本上,在 MyClass 对象上进行一些耗时的计算。如果计算已经在一段时间前使用完全相同的值完成,我想节省这次时间。为此,如果结果已经可用,我想在 HashMap 中查找。
Would you be using MyClass in a HashMap as the key or as the value? If the key, you have to override both equals() and hashCode()
因此,我使用 MyClass 的 hashCode 作为 HashMap 中的 key。值(计算结果)将有所不同,例如 Integer(简化)。
What do you think equality should mean for multiple collections? Should it depend on element ordering? Should it only depend on the absolute elements that are present?
这不会取决于存储在 coll
中的集合类型吗?虽然我猜顺序不是很重要,不
您从该站点获得的响应非常棒。谢谢大家
@AlexWien that depends on whether that collection's items are part of the class's definition of equivalence or not.
是的,是的。
- I'll have to go into all fields and respective parent classes recursively to make sure they all implement
hashCode()
properly, because otherwisehashCode()
ofMyClass
might not take into consideration some values. Is this right?
没错。它并不像听起来那么繁琐,因为经验法则是如果您覆盖 equals()
,则只需要覆盖 hashCode()
。您不必担心使用默认 equals()
的 class;默认的 hashCode()
就足够了。
此外,对于您的 class,您只需要散列您在 equals()
方法中比较的字段。例如,如果这些字段之一是唯一标识符,您只需在 equals()
中检查该字段并将其散列在 hashCode()
.
所有这一切都取决于您是否也压倒了 equals()
。如果你没有覆盖它,也不要打扰 hashCode()
。
- What do I do with that
Collection
? Can I always rely on itshashCode()
method? Will it take into consideration all child values that might exist in mysomeInterface
object?
是的,您可以依靠 Java 标准库中的任何集合类型来正确实现 hashCode()
。是的,任何 List
或 Set
都会考虑其内容(它将项目的哈希码混合在一起)。
根据您的说明:
您想将 MyClass
作为键存储在 HashMap
中。
这意味着 hashCode
() 在添加对象后不允许更改。
因此,如果您的集合在对象实例化后可能会发生变化,则它们不应成为 hashcode() 的一部分。
来自http://docs.oracle.com/javase/8/docs/api/java/util/Map.html
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
对于 20-100 个对象,不值得冒 hash() 或 equals() 实现不一致的风险。
在您的情况下无需重写 hahsCode() 和 equals()。 如果您不覆盖它,java 会采用 equals 和 hashcode() 的唯一对象标识(这很有效,特别是因为您声明考虑到对象字段的值不需要 equals() ).
使用默认实现时,您是安全的。
当哈希码在插入后更改时,在 HashMap 中使用自定义 hashcode() 作为键等错误,因为您使用集合的 hashcode() 作为对象哈希码的一部分可能会导致极难找到错误。
如果需要查重计算是否完成,我不会absue equals()。只需编写自己的方法 objectStateValue()
并在集合上调用 hashcode() 即可。这不会干扰对象 hashcode 和 equals()。
public int objectStateValue() {
// TODO make sure the fields are not null;
return 31 * s.hashCode() + coll.hashCode();
}
另一种更简单的可能性:执行耗时计算的代码可以在计算准备就绪后立即将 calculationCounter 增加 1。然后您只需检查计数器是否已更改。这样更便宜也更简单。
所以您想对对象的内容进行计算,这将为您提供一个唯一的键,您将能够在 HashMap
中检查 "heavy" 计算是否是您 不要 已针对给定的深度字段组合执行两次操作。
单独使用hashCode
:
我认为 hashCode
不适合用于您所描述的场景。
hashCode
应该 总是 与 equals()
结合使用。这是它的契约的一部分,而且是一个重要的部分,因为 hashCode()
return 是一个整数,尽管人们可能会尝试使 hashCode()
尽可能均匀分布,但它不会对于相同 class 的每个可能对象都是唯一的,除了非常特殊的情况(例如 Integer
、Byte
和 Character
很容易...)。
如果您想亲自查看,请尝试生成最多 4 个字母(小写和大写)的字符串,并查看其中有多少具有相同的哈希码。
HashMap
因此在查找散列 table 中的内容时同时使用 hashCode()
和 equals()
方法。将有具有相同 hashCode()
的元素,您只能通过使用 equals()
对您的 class.
同时使用 hashCode
和 equals
在这种方法中,您使用对象本身作为哈希映射中的键,并为其提供适当的 equals
方法。
要实施equals
方法,您需要深入到您的所有领域。为了你的大计算,他们所有的 classes 必须有 equals()
匹配你认为相等的东西。当您的对象实现接口时需要特别小心。如果计算是基于对该接口的调用,并且实现该接口的不同对象 return 在这些调用中具有相同的值,那么它们应该以反映该接口的方式实现 equals
。
并且它们的 hashCode
应该与 equals
匹配 - 当值相等时,hashCode
必须相等。
然后您根据所有这些项目构建您的 equals
和 hashCode
。您可以使用 Objects.equals(Object, Object)
和 Objects.hashCode( Object...)
来节省大量样板代码。
但这是一个好方法吗?
虽然您可以在对象中缓存 hashCode()
的结果并在不对其进行变异的情况下重新使用它,但您不能对 equals
执行此操作。这意味着 equals
的计算将会很长。
因此,根据每个对象调用 equals()
方法的次数,这种情况会加剧。
例如,如果 hashMap
中有 30 个对象,但将出现 300,000 个对象并与它们进行比较时才意识到它们与它们相等,你'将进行 300,000 次重比较。
如果您只有很少的实例,其中一个对象将具有相同的 hashCode
或属于 HashMap
中的同一个桶,需要比较,然后继续equals()
方法可能会很有效。
如果你决定走这条路,你需要记住:
如果对象是 HashMap
中的键,只要它存在,就应该不 被改变。如果你需要改变它,你可能需要对它进行深拷贝并将副本保存在哈希映射中。又是深拷贝,需要考虑里面所有的对象和接口,看是否完全可拷贝。
为每个对象创建唯一键
回到您最初的想法,我们已经确定 hashCode
不是散列映射中键的良好候选者。一个更好的候选者是散列函数,例如 md5
或 sha1
(或更高级的散列,如 sha256,但在您的情况下不需要加密强度),其中冲突很多比 int
更稀有。您可以将 class 中的所有值,将它们转换为字节数组,使用这样的哈希函数对其进行哈希处理,并将其十六进制字符串值作为您的映射键。
这自然不是小菜一碟。所以你需要考虑它是否真的为你试图避免的计算节省了很多时间。它可能比重复调用 equals()
来比较对象更快,因为每个实例只执行一次,与它在 "big calculation".
对于给定的实例,您可以缓存结果并且不再计算它,除非您改变对象。或者您可以仅在执行 "big calculation".
之前再次计算它但是,您需要 class 中所有对象的 "cooperation"。也就是说,它们都需要合理地转换为字节数组,以使两个等效对象产生相同的字节(包括我上面提到的接口对象的相同问题)。
你还应该注意你有两个字符串 "AB" 和 "CD" 的情况,它们会给你与 "A" 和 [=115= 相同的结果],然后您将得到两个不同对象的相同散列。
对于未来的读者。
是的,equals 和 hashCode 齐头并进。
下面显示了使用辅助库的典型实现,但它确实显示了 "hand in hand" 的性质。来自 apache 的帮助程序库使事情变得更简单恕我直言:
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyCustomObject castInput = (MyCustomObject) o;
boolean returnValue = new org.apache.commons.lang3.builder.EqualsBuilder()
.append(this.getPropertyOne(), castInput.getPropertyOne())
.append(this.getPropertyTwo(), castInput.getPropertyTwo())
.append(this.getPropertyThree(), castInput.getPropertyThree())
.append(this.getPropertyN(), castInput.getPropertyN())
.isEquals();
return returnValue;
}
@Override
public int hashCode() {
return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
.append(this.getPropertyOne())
.append(this.getPropertyTwo())
.append(this.getPropertyThree())
.append(this.getPropertyN())
.toHashCode();
}
17、37 .. 您可以选择自己的值。