hashcode()和equals()的默认实现
- 首先hashcode()方法和equal()方法都可以用来对比两个对象是否相等一致。
- 在默认情况下,使用equals方法比较两个元素比较的是两个对象的地址与==等价。对于string类对象它重写了equals方法所以比较的是字符串的内容。
- hashCode()方法返回的就是一个数值表示的hash码。默认情况下,不同对象的哈希码是不同的。
equals()方法的覆写
- 我们已经知道了,默认情况下equals()方法等价于==,它只能判断两个对象的引用地址是否相同,无法判断两个对象的内容是否相同。
- 一般情况下,我们都会对自定义类的equals方法进行覆写,从而判断两个对象的内容是否相同。
- 具体的覆写方法如下:首先判断传入的对象是否为空,然后判断两个对象引用地址是否相同,再判断两个对象的类型是否一致,最后判断两个对象各个域的内容是否一样。
hashcode方法的重写
- 一般在将对象做为元素存储到HashSet集合中时,必须要对equals和HashSet进行重写。
HashSet保证元素唯一性的原理
- 通常如果要保证序列的唯一性,在添加元素的时候要和序列中已经存在的元素一一对比,如果不重复再进行添加,如果重复就不添加了。但是如果序列的元素过多这种方法效率就会十分低下。
- 而HashSet底层是这样实现的:首先HashSet的底层是一个数组加链表组成的散列表,每个数组元素存储一个链表的头节点。
- 在HashSet中新添加一个元素时,先根据元素的hash值计算一个下标值,然后判断该下标处是否已经存储元素,如果不存在就直接存储,如果存在就和已经存在的元素一一比较它们的hash值。如果新元素的hash值和该下标处所有元素的下标值都不同,就在对应链表中存储新元素,否则使用equals比较元素的内容是否相同,如果不同就存储新元素,如果相同就说明元素重复,不进行添加。
hashcode方法在HashSet集合中的使用
- 通过以上说明,我们已经明白了HashSet如何保持元素不重复了。
- 现在我们使用HashSet存储一下字符串试试:
HashSet<String> h = new HashSet<>();
h.add("张三");
h.add("李四");
h.add("王大锤");
h.add("张三");
for(String s:h){
System.out.println(s);
}
- 我们使用HashSet存储了四个字符串,其中两个重复,最后我们发现重复的元素不见了,说明HashSet保证了元素的唯一性。
- 现在让我们存储自定义的实例对象试试看。
HashSet<Student> set = new HashSet<>();
set.add(new Student("张三",18));
set.add(new Student("李四",15));
set.add(new Student("王大锤",15));
set.add(new Student("张三",18));
for(Student s:set){
System.out.println(s.name+"--"+s.age);
}
class Student{
public String name;
public int age;
Student(String name,int age){
this.name = name;
this.age = age;
}
}
- 以上代码我们定义了一个学生类,并实例化了四个学生对象进行存储,其中两个学生是重复的。可是最后我们发现四个对象都存储了,HashSet中有两个张三,这是为什么呢?
- 因为我们没有重写hashcode和equals方法,这时候第一个对象和第四个对象的hash值和地址都是不相同的,因为地址不相同所以程序认为这两个对象不相同。
- 这时候当存储第四个对象的时候,系统先判断它的hash值,发现它的hash值和已经存在的三个元素的hash值不同,所以就会直接存储。
- 所以在重写hashcode方法时必须要保证第一个元素与第四个元素的hash值相同。为了简单起见我们重写hashcode时直接return 1,equals方法比较对象各成员是否相等。
class Student{
public String name;
public int age;
Student(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
public int hashCode() {
return 1;
}
}
- 重写了两个方法后我们发现元素不重复了,但是还有一个问题,在存储元素时四个对象的hash都一样,它们的存储位置都一样,说明所有元素都堆积到了一个下标,空间利用率极低。所以我们重写hashCode方法时既要保证相同元素的hash值相同,也要保证不同元素的hash值尽可能不同,这样才能保证元素在散列表存储时尽可能分散。
- 系统自动生成的hashcode方法是这样的
public int hashCode() {
return Objects.hash(name, age);
}
总结
- 在覆写equals方法前,它比较的是两个对象的地址(strings除外),覆写之后可以用来比较两个对象内容是否一样。
- hashcode方法只有在set集合中用到。
- 重写的hashcode方法的特点:两个元素hash值不同,元素一定不相同,两个元素的hash值相同,元素可能不相同。