我试图理解为什么JVM的默认实现不会为所有对象返回相同的hashcode()
值...
我编写了一个程序,我已经覆盖equals()
而不是hashCode()
,后果是可怕的。
HashSet
即使等号相同也会添加两个对象。TreeSet
在Comparable实施中抛出异常.. 还有更多..
如果默认Object'shashCode()
实现返回相同的int值,则可以避免所有这些问题......
我理解他们很多关于hashcode() and equals()
的书面和讨论,但我无法理解为什么默认情况下无法处理的事情,这很容易出错,后果可能真的很糟糕和可怕......
这是我的示例程序..
import java.util.HashSet;
import java.util.Set;
public class HashcodeTest {
public static void main(String...strings ) {
Car car1 = new Car("honda", "red");
Car car2 = new Car("honda", "red");
Set<Car> set = new HashSet<Car>();
set.add(car1);
set.add(car2);
System.out.println("size of Set : "+set.size());
System.out.println("hashCode for car1 : "+car1.hashCode());
System.out.println("hashCode for car2 : "+car2.hashCode());
}
}
class Car{
private String name;
private String color;
public Car(String name, String color) {
super();
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Car other = (Car) obj;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
输出:
Set的大小:2
car1的hashCode:330932989
car2的hashCode:8100393
答案 0 :(得分:3)
hashcode和equals应该以这样的方式编写,当equals返回true时,这些对象具有相同的哈希码。
如果覆盖等于,则必须提供正常工作的哈希码。
默认实施无法处理,因为默认实施不知道哪些字段很重要。并且自动实现不会以有效的方式执行,哈希码函数是加速数据结构中的数据查找等操作,如果实现不当,则性能将受到影响。
答案 1 :(得分:3)
似乎您希望默认只计算所有对象字段并使用某些公式组合其hashCodes来计算hashCode
。这种做法是错误的,可能导致许多不愉快的情况。在你的情况下它会工作,因为你的对象非常简单。但是现实生活中的对象要复杂得多。几个例子:
将对象连接到双链表(每个对象都有previous
和next
个字段)。默认实现如何计算hashCode?如果它应该检查字段,它将以无限递归结束。
好的,假设我们可以检测到无限递归。我们只有单链表。在这种情况下,应该从所有后继节点计算每个节点的hashCode?如果此列表包含数百万个节点怎么办?应该检查所有这些以生成hashCode? p>
假设您有两个HashSet
个对象。首先创建如下:
HashSet<Integer> a = new HashSet<>();
a.add(1);
第二个是这样创建的:
HashSet<Integer> b = new HashSet<>();
for(int i=1; i<1000; i++) b.add(i);
for(int i=2; i<1000; i++) b.remove(i);
从用户的角度来看,两者都只包含一个元素。但是在编程方面,第二个包含大的哈希表(如2048个条目的数组,其中只有一个不为空),因为当你添加了许多元素时,哈希表被调整大小。相反,第一个保持内部的小散列表(例如16个元素)。所以编程对象是非常不同的:一个有大数组,另一个有小数组。但由于hashCode
和equals
的自定义实现,它们是相同的且具有相同的hashCode。
假设您有不同的List
实现。例如,ArrayList
和LinkedList
。两者都包含相同的元素,从用户的角度来看,它们是相同的,并且应该具有相同的hashCode。它们确实相同并且具有相同的hashCode。但是它们的内部结构完全不同:ArrayList
包含一个数组,而LinkedList
包含指向表示head和tail的对象的指针。因此,您不能仅根据其字段生成hashCode:它肯定会有所不同。
某些对象可能包含延迟初始化的字段(初始化为null并仅在必要时从其他字段计算)。如果你有两个相同的对象并且其中一个的懒惰字段被初始化而另一个不是,那该怎么办?我们应该从hashCode计算中排除这个惰性字段。
因此,在很多情况下,通用hashCode方法不起作用,甚至可能产生问题(例如使程序因StackOverflowError
崩溃或卡住枚举所有链接对象)。因此,选择了基于对象标识的最简单的实现。请注意,hashCode
和equals
的合同要求它们保持一致,并且默认实现已完成。如果您重新定义equals
,则必须重新定义hashCode
。
答案 2 :(得分:0)
来自Docs
尽可能合理,Object类定义的hashCode方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但JavaTM编程语言不需要此实现技术。)
答案 3 :(得分:0)
来自文档:
如果两个对象根据等于(对象)相等 方法,然后在每个上调用hashCode}方法 这两个对象必须产生相同的整数结果。
然后如果覆盖equals()的行为方式,则必须覆盖hashCode()。
另外,来自equals()的文档 -
请注意,通常需要覆盖hashCode 方法何时重写此方法,以便维护 hashCode方法的一般合同,其中说明 等于对象必须具有相同的哈希码。
答案 4 :(得分:0)
来自Object
类的javadoc:
返回对象的哈希码值。支持此方法是为了哈希表的优势,例如HashMap提供的哈希表。
因此,如果默认实现提供相同的哈希,它就会失败。
对于默认实现,它不能假设所有类都是值类,因此来自doc的最后一句:
尽可能合理,Object类定义的hashCode方法确实为不同的对象返回不同的整数。