在我看到的关于重写的equals和hashcode方法的所有问题中,人们总是说,如果重写equals方法,那么也应该重写hashcode方法,反之亦然,以避免从哈希集合获取对象时出现问题。
就我个人而言,我认为反之亦然。让我们考虑一下,我们使用一个对象(带有属性)作为HashMap中的键,并且我们不需要测试该对象的两个实例之间的相等性。
如果我们以一种有效的方式(基于属性和其他规则)重写hashcode方法,那么我们就可以拥有唯一的键,在这种情况下,对于HashMap来说,我们将在每个bucket中拥有唯一的值,并且HashMap将不会使用equals方法来比较bucket中的值,因为我们在每个bucket中都有一个值。
合同方:
我错了吗?
编辑:(搜索后,答案下方,并查找HashMap的源代码)
为什么我们也要重写equals方法?
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
如果我们不重写equals,如果我们失去键的引用,例如对另一个代码不可见的键引用,我们将无法从HashMap中检索值。读取HashMap的唯一方法是使用迭代器,再加上比较值以检索精确的对:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Steven”)]
Second put: [key:Instance2Obj(17488696), value:Person2Obj(“Hewlett”, “Emily”)]
如果我们想根据Baron Steven的帐号检索他的帐号,我们不能仅仅创建一个具有相同帐号的新对象(例如Instance88Obj(144756696)并调用get方法来检索值。在这种情况下,我们将得到一个空结果,因为get方法使用了基于键引用的比较,即使hashcode将是相同的。
如果未重写equals,则通过一个新的实例键(但具有相同的hashcode)来更新该值将不起作用,只会添加另一对键/值:
First put: [key:Instance1Obj(144756696), value:Person1Obj(“Baron”, “Seven”)]
Second put: [key:Instance2Obj(144756696), value:Person2Obj(“Baron”, “Steven”)]
通常我们希望用“steven”代替值“seven”,但这种替换不会发生
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
结论:
在标准的Javahashmap
中,总是使用equals
来确定密钥是否在bucket中。如果hashcode
只给出了一个项的right bucket,但equals
针对该项返回false,这意味着该项不在其中。
如果不重写equals
,它将使用对象引用。如果您希望使用对象的值作为键(也就是说,您可以使用具有相同值的不同对象实例),而不需要完全相同的对象实例(相同的实例不相等),那么您需要编写equals
来实现比较值。
默认hashcode
与默认equals
一样高效。仅重写hashcode
可能会降低性能,而不改变行为(equals
仍然确定两个关键对象是否相等)。这样做毫无意义。
您没有这样问,但是另外,如果您重写equals
而不重写hashcode
,那么您就会破坏hashmap
键,因为hashcode
可能会将您带到错误的bucket,equals
将找不到键,因为它只检查一个bucket。
您可以做的:如果您可以保证每个项目都有不同的hashcode
,那么您可以编写equals
,它只比较哈希代码。如果在对象中缓存哈希代码,那么这可以提高性能,使equals
只比较两个int。当然还有内存开销。
问题内容: 您能否澄清一下,为什么在我们将 final 关键字设为不变时,为什么 在上课之前需要 final 关键字。我的意思是,如果我们将所有属性声明为私有和最终的,那么它也是一个不可变的类,不是吗? 很抱歉,这个问题似乎很简单,但是我对此感到非常困惑。帮帮我。 编辑:我知道一个声明为final的类不能被子类化。但是如果每个属性都是私有和final的,那有什么区别呢? 问题答案: 正如堆纸器所说
标题说明了一切。我的问题是关于不同的字符串等价方法的效率。我经常使用<代码>。equalsIgnoreCase(String str),因为我对它很有兴趣。但我开始怀疑这是否是寻找字符串之间等价性的最有效方法。在我看来,正在调用一种大小写转换方法或,然后在其定义中调用,但我可能错了。那么,以下哪种方法在以下情况或任何情况下更有效?
问题内容: 我必须删除列表中的重复对象。这是来自对象Blog的列表,如下所示: 复制的对象是标题,作者,URL和描述与其他对象相同的对象。 而且我无法更改对象。我不能在上面放新方法。 我该怎么做呢? 问题答案: 如果您不能编辑类的源代码(为什么不这样做),则需要遍历列表并根据提到的四个条件(“标题,作者,URL和描述”)比较每个项目。 为了以一种高效的方式做到这一点,我将创建一个新类,类似于包含这
问题内容: 我正在阅读Java 1.6 API提供的HashMap类的代码,无法完全理解以下操作的需要(位于put和get方法的主体中): 该方法具有以下主体: 通过对提供的哈希码执行位操作,可以有效地重新计算哈希。即使API声明如下,我也无法理解这样做的必要性: 这很关键,因为HashMap使用2的幂的哈希表,否则哈希表在低位无差异时会遇到冲突。 我确实知道键值参数存储在数据结构数组中,并且该数
问题内容: 我正在从xml配置转移到注释。我想转换一个会话范围的bean是 可以通过注释完成此操作吗?如果没有,我该怎么做才能使该声明继续工作? 问题答案: 在spring上下文xml中,执行以下操作: 请注意,尽管如此,你将需要为该包中的所有类编写接口。
我正在开发一款Android应用程序。在我的应用程序中,我集成了Facebook登录。我的facebook登录工作正常。但当我制作release apk并运行该应用程序并尝试登录Facebook时,它就不工作了。请看下面我的场景。 我生成如下的发布apk 然后我使用jks文件路径生成keyhash。 我得到了一个散列键,然后将其添加到开发人员配置文件设置中。 当我在我的设备上安装并运行apk并使用