鉴于我的课程中包含各种字段:
class MyClass {
private String s;
private MySecondClass c;
private Collection<someInterface> coll;
// ...
@Override public int hashCode() {
// ????
}
}
其中,我确实有各种各样的对象,我想在HashMap
中存储。为此,我需要hashCode()
MyClass
。
我必须以递归的方式进入所有字段和各个父类 ,以确保它们都正确实现hashCode()
,否则hashCode()
MyClass
可能不会考虑某些值。这是对的吗?
我该怎么处理Collection
?我可以一直依赖hashCode()
方法吗?是否会考虑我的someInterface
对象中可能存在的所有子值?
我打开了关于在此处唯一身份识别对象的实际问题的第二个问题:How do I generate an (almost) unique hash ID for objects?
澄清:
如果其中一个对象的你班上有什么或多或少的不合理吗?字符串s?然后只将其用作哈希码。
coll
中的任何值发生了变化,那么两个对象的MyClass hashCode()肯定会有所不同。如果两个对象的所有字段都存储相同的值,HashCode应该只返回相同的值。基本上,在MyClass对象上进行一些耗时的计算。如果计算已经使用完全相同的值在前一段时间完成,我想多余时间。为此,如果结果已经可用,我想查看HashMap。
你会在HashMap中使用MyClass作为键还是值?如果是键,则必须覆盖equals()和hashCode()
因此,我在HashMap中使用hashCode OF MyClass作为键。值(计算结果)将是不同的,如整数(简化)。
您认为平等对多个馆藏意味着什么?它应该依赖于元素排序吗?它应该只取决于存在的绝对元素吗?
这取决于coll
中存储的Collection类型吗?虽然我觉得订购并不重要,但没有
您从本网站获得的回复非常华丽。谢谢大家
@AlexWien取决于该集合的项目是否属于该类别的等同定义。
是的,他们是。
答案 0 :(得分:7)
- 我必须递归进入所有字段和各个父类递归,以确保它们都正确实现
醇>hashCode()
,否则hashCode()
MyClass
可能没有考虑到某些值。这是对的吗?
那是对的。它并不像听起来那么繁重,因为经验法则是,如果您覆盖hashCode()
,则只需覆盖equals()
。您不必担心使用默认equals()
的类;默认hashCode()
就足够了。
此外,对于您的课程,您只需要在equals()
方法中对您比较的字段进行哈希处理。例如,如果其中一个字段是唯一标识符,则只需在equals()
中检查该字段并在hashCode()
中对其进行哈希处理即可。
所有这一切都取决于您是否覆盖equals()
。如果你没有覆盖它,也不要为hashCode()
而烦恼。
- 我如何处理
醇>Collection
?我可以一直依赖hashCode()
方法吗?是否会考虑我的someInterface
对象中可能存在的所有子值?
是的,您可以依赖Java标准库中的任何集合类型来正确实现hashCode()
。是的,任何List
或Set
都会考虑其内容(它会将项目和哈希代码混合在一起)。
答案 1 :(得分:2)
因此,您希望对对象的内容进行计算,这将为您提供一个唯一的密钥,您可以在HashMap
中检查&#34;重&#34;对于给定的深层字段组合,您已经完成了两次 想要做两次的计算。
hashCode
:我认为hashCode
不适合在您描述的场景中使用。
hashCode
应始终与equals()
关联使用。它是合同的一部分,而且它是一个重要的部分,因为hashCode()
会返回一个整数,尽管有人可能会尝试使hashCode()
尽可能地分布,但它对于同一个类的每个可能的对象都不会是唯一的,除非是非常具体的情况(Integer
,Byte
和Character
很容易。 )。
如果您想亲眼看看,请尝试生成最多4个字母(大写和小写)的字符串,并查看其中有多少字符具有相同的哈希码。
因此, HashMap
在哈希表中查找内容时会同时使用hashCode()
和equals()
方法。将会有一些具有相同hashCode()
的元素,您只能通过使用equals()
对您的班级测试所有元素来判断它们是否相同。
hashCode
和equals
在这种方法中,您将对象本身用作哈希映射中的键,并为其提供适当的equals
方法。
要实施equals
方法,您需要深入了解所有字段。他们所有的班级都必须equals()
符合您的想法,以便进行大规模计算。当对象实现接口时,需要特别小心。如果计算基于对该接口的调用,并且实现该接口的不同对象在这些调用中返回相同的值,那么它们应该以反映该值的方式实现equals
。
他们的hashCode
应与equals
匹配 - 当值相等时,hashCode
必须相等。
然后,您可以根据所有这些项目构建equals
和hashCode
。您可以使用Objects.equals(Object, Object)
和Objects.hashCode( Object...)
来节省很多样板代码。
但这是一个好方法吗?
虽然您可以将hashCode()
的结果缓存在对象中并重新使用它而不进行计算,只要您不改变它,就不能为{{1}执行此操作}。这意味着equals
的计算将会很长。
因此,根据每个对象调用equals
方法的次数,这将会加剧。
例如,如果您要在equals()
中拥有30个对象,但是300,000个对象将会出现并与它们进行比较,只是为了意识到它们与它们相等,那么你就是&#39;将进行300,000次重磅比较。
如果你只有很少的实例,其中一个对象将具有相同的hashMap
或落入hashCode
中的同一个桶中,需要进行比较,那么就去HashMap
方式可能效果很好。
如果你决定这样做,你需要记住:
如果对象是equals()
中的某个键,只要它在那里就不突变。如果您需要改变它,您可能需要对其进行深层复制并将副本保留在哈希映射中。深度复制需要考虑内部的所有对象和接口,以查看它们是否可以复制。
回到最初的想法,我们已经确定HashMap
不适合哈希映射中的密钥。一个更好的候选者是哈希函数,例如hashCode
或md5
(或更高级的哈希值,例如sha256,但你不需要在你的情况下加密强度),其中碰撞是比sha1
更为罕见。您可以获取类中的所有值,将它们转换为字节数组,使用此类哈希函数对其进行哈希处理,并将其十六进制字符串值作为映射键。
当然,这不是一个微不足道的计算。因此,您需要考虑它是否真的为您节省了大量时间,而不是您想要避免的计算。它可能比重复调用int
来比较对象更快,因为每个实例只执行一次,并且在&#34;大计算&#34;时具有的值。
对于给定的实例,除非改变对象,否则可以缓存结果而不再计算结果。或者你可以在进行&#34;大计算&#34;。
之前再次计算它然而,您需要&#34;合作&#34;你班上所有的物品。也就是说,它们都需要合理地转换为字节数组,使得两个等效对象产生相同的字节(包括与上面提到的接口对象相同的问题)。
你还应该注意你拥有的情况,例如,两个字符串&#34; AB&#34;和&#34; CD&#34;这将给你与&#34; A&#34;相同的结果。和&#34; BCD&#34;,然后你最终得到两个不同对象的相同哈希值。
答案 2 :(得分:0)
供将来的读者使用。
是的,等于和hashCode齐头并进。
下面显示了使用帮助程序库的典型实现,但实际上显示了“手拉手”性质。 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 ..您可以选择自己的值。
答案 3 :(得分:-1)
从你的澄清:
您希望将MyClass
存储在HashMap
作为密钥。
这意味着添加对象后不允许更改hashCode
()。
因此,如果您的集合可能在对象实例化后发生更改,则它们不应该是hashcode()的一部分。
来自http://docs.oracle.com/javase/8/docs/api/java/util/Map.html
注意:如果将可变对象用作地图,则必须非常小心 键。如果对象的值,则不指定映射的行为 以一种影响等于比较的方式改变 对象是地图中的一个关键。
对于20-100个对象,不值得您输入hash()或equals()实现不一致的风险。
在您的情况下,无需覆盖hahsCode()和equals()。 如果你不克服它,java会获取equals和hashcode()的唯一对象标识(这是有效的,因为你声明你不需要等于()考虑对象字段的值)。
使用默认实现时,您可以放心使用。
在插入后哈希码更改时,使用自定义哈希码()作为HashMap中的键时出错,因为您使用集合的哈希码()作为对象哈希码的一部分可能会导致极难找到错误
如果您需要确定重量计算是否已完成,我不会 absue equals()。只需编写一个自己的方法objectStateValue()
并在集合上调用hashcode()。这样就不会干扰对象hashcode和equals()。
public int objectStateValue() {
// TODO make sure the fields are not null;
return 31 * s.hashCode() + coll.hashCode();
}
另一个更简单的可能性:执行耗时计算的代码可以在计算准备就绪后立即将calculateCounter提高一。然后,您只需检查计数器是否已更改。这更便宜,更简单。