在解释为什么要重写equals方法和hashcode方法时,我们要先了解一下这样重写的目的是什么,也让自己有一个思路,围绕这个思路去思考问题,能更好的整握其中的缘由,针对这个目的去思考为什么要去重写,重写的作用是什么,以及如何去重写它们。
一、重写equals方法和hashcode方法的目的
在做对象间的比较时,我们通常采用equals方法来判断它们是否相等,如果地址值不同,则俩个对象不等。那为什么我们还要重写这俩个方法,换句话说,重写这俩个方法的目的在哪。
首先,我们思考这样一个需求:当一个数据表中有A和B俩个人,只要他们的名字和身份证这俩个字段相等,那么我们就认定这俩个人是同一个人,不去考虑他们的地址值是否相等来比较这俩个人是不是同一个人。
再有,这样一个情形:我们用一个拼接好的hello world的字符串和一个new出来的hello world作比较,只要内容一样,则俩个变量相等,如果只用equals来比较的话,他们势必不相等。
所以,重写equals方法和hashcode方法的目的就是为了实现一些合乎情理,切实际,在现实生活中经常出现的一些情景,针对这些情景来提出一些需求,为了满足这个需求从而采取的措施。如果不重写的话,在他的源码中比较的是俩个对象的地址值。
二、为什么要同时重写equals方法和hashcode方法
了解目的之后,所以我们需要重写这俩个方法,那只重写其中的一个方法可行嘛?我们来分析一下:
只重写equals方法:这种方法是可行的,但是假如有这样一个情况,在做俩个对象A和B的比较时,B对象是存放在集合里的,且集合里的数据多达几万条,那我们还要调用equals方法来一个一个比较嘛,当然可以,但是这样的话,效率就必然成了一个问题,所以,这个时候,就需要我们的hashcode方法了,使用对象生成的hashcode可以高效的比对对象的值是否相等。
只重写hashcode方法呢:在做对象之间的比较时,我们要知道:
如果equals方法相等,则他们hsahcode的hash值必然相等,俩个对象必然相等
如果equals方法不等,则他们hashcode的hash值不一定不等,俩个对象必然不等
如果hashcode的hash值不等,则俩个对象必然不等
如果hashcode的hash值相等,俩个对象不一定相等,因为hash算法很难完全避免hash碰撞的问题,所以要使用equals方法来进行托底,判断比较俩个对象是否相等。
所以,需要同时重写equals方法和hashcode方法。
三、重写equals方法和hashcode方法的作用
其实,如果看懂了上文的话,这里也就知道了重写这俩个方法的作用,
第一点:就是为了更加高效的去比较俩个对象是否相等。
第二点:实现特殊的需求。
四、如何重写equals方法和hashcode方法
在重写的过程中,我们要遵守equals方法和hashcode方法的特性,也就是要一一证明它的特性。
1.equals方法的特性:
(1)自反性:对于任何非空引用x,x.equals(x)应该返回true;
(2)对称性:对于任何引用x,和y,当且仅当,y.equals(x)返回true,x.equals(y)也应该返回true;
(3)传递性:对于任何引用x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true;
(4)一致性:如果x,y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果;
(5)对于任意非空引用x,x.equals(null)返回false;
2.而hashcode方法的特性:
(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址;
(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;
(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。
(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写,以保证equals方法相等时两个对象hashcode返回相同的值(eg:Set集合中确保自定义类的成功去重)。
3.如何重写equals方法和hashcode方法
自己摸索,哈哈哈。
public class User {
private String id;
private String name;
private String age;
public User(){
}
public User(String id, String name, String age){
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return this.id + " " + this.name + " " + this.age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;//地址相等
}
if(obj == null){
return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。
}
if(obj instanceof User){
User other = (User) obj;
//需要比较的字段相等,则这两个对象相等
if(equalsStr(this.name, other.name)
&& equalsStr(this.age, other.age)){
return true;
}
}
return false;
}
private boolean equalsStr(String str1, String str2){
if(StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)){
return true;
}
if(!StringUtils.isEmpty(str1) && str1.equals(str2)){
return true;
}
return false;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + (age == null ? 0 : age.hashCode());
return result;
}
}
五、只重写equals不重写hashcode会出现什么问题?
违反hashCode的常规协定:根据Java文档,当两个对象通过equals方法比较为相等时,它们的hashCode方法应该返回相同的整数值。如果你重写了equals而没有相应地重写hashCode,这个约定就被打破了。
哈希集合行为异常:在哈希集合中,对象是通过它们的哈希码分组的。如果两个对象通过equals方法相等,但由于hashCode方法没有被重写,它们返回不同的哈希码,这将导致它们被错误地存储在集合的不同位置。这样,即使equals方法表明这两个对象是相等的,集合仍然会将它们视为不同的元素。
影响集合性能:不正确的hashCode实现会导致哈希冲突增多,降低哈希表基础结构(如HashMap)的性能。理想情况下,哈希码应该均匀分布以减少冲突,但如果没有正确重写hashCode,这种分布可能无法实现。
数据丢失风险:在使用如HashMap或HashSet等集合时,如果你试图检索一个已经存储的对象,由于hashCode方法的不一致,可能无法正确地找到这个对象,即使它实际上存在于集合中。
六、默认的hashcode是什么样的?
在Java中,Object类的默认hashCode方法是用来返回对象的内存地址的哈希值,或者至少是一个基于内存地址的值。这个实现确保了在Java的默认行为下,不同的对象会有不同的哈希码。
具体来说,Object类的hashCode方法的特点如下:
基于内存地址:默认的hashCode方法通常返回对象的内存地址或者这个地址的某种转换形式。这意味着不同的对象实例通常会有不同的哈希码。
原生方法:这个方法是一个原生(native)方法,它的具体实现依赖于底层的Java虚拟机(JVM)实现。
一致性:对于同一个对象多次调用hashCode方法,只要对象的信息没有被修改,它必须始终返回相同的值。
唯一性:虽然不同的对象实例通常会有不同的哈希码,但Java并不保证这种唯一性。不同的对象可以有相同的哈希码,这称为哈希碰撞。
当你重写equals方法而不重写hashCode方法时,就会打破hashCode方法的一般约定,因为按照Java文档,相等的对象必须具有相等的哈希码。如果你使用默认的hashCode方法,即使两个对象通过equals方法比较为相等,它们可能仍然会有不同的哈希码,因为它们位于内存的不同位置。这就是为什么重写equals的同时重写hashCode是非常重要的。
七、举出一个实际的例子来说明,只重写equals不重写hashcode带来的问题?
让我们通过一个实际的例子来说明只重写equals方法而不重写hashCode方法时可能出现的问题。假设我们有一个简单的Person类,其中包含两个属性:name和age。我们将重写equals方法来判断两个Person对象是否相等,但不重写hashCode方法。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
// hashCode 方法没有被重写
}
现在,让我们在一个HashSet中使用Person对象:
public class Main {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<>();
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
set.add(p1);
System.out.println("Added p1, set contains p1: " + set.contains(p1)); // true
System.out.println("Set contains p2: " + set.contains(p2)); // false
set.add(p2);
System.out.println("Set size after adding p2: " + set.size()); // 2
}
}
在这个例子中,p1和p2具有相同的name和age值,因此根据我们的equals方法,它们应该被视为相等。然而,由于我们没有重写hashCode方法,每个对象将具有基于它们内存地址生成的不同哈希码。
当我们尝试将p2添加到HashSet时,尽管它与p1“相等”,HashSet仍然会将它作为一个新元素添加进去,因为它们具有不同的哈希码。这导致set中有两个看似相等的Person实例,违反了HashSet的预期行为,即不允许重复元素。
这就是为什么当你重写equals方法时,也应该重写hashCode方法,以保证这些基于哈希的集合类能够正确地根据你定义的相等性逻辑来工作。在这个例子中,如果我们同时重写hashCode方法,确保对于相等的Person对象返回相同的哈希值,HashSet就会按预期工作,不允许添加重复的Person实例。