关于hashCode()方法和equals()方法的重写
- 我们向Set集合中添加的对象,这个对象的所在类中一定要重写equals()方法和hashCode()方法
- 这里我们不一定要重写hashCode()方法,因为我们的TreeSet类中我们我们判断数据是否重复是通过Comparable接口中的compareTo()方法或者是通过Comparator接口中的compare()方法的返回值来判断的,这个时候我们并没有使用到hashCode()方法,但是equals()方法我们一定要重写,因为我们要去判断我们使用有的方法时就要通过equals()方法进行判断
- 其实我们推荐在重写equals()方法的时候就一并重写hashCode()方法
java官方提示:我们的hashCode()方法应该时刻和equals()方法保持一致性
这里的一致性:指的就是我们相同的对象具有相同的哈希码值
- 我们的Object类中的hashCode()方法我们就可以看作是一个随机值函数,随机产生一个哈希码值
- 我们的Object 类中的hashCode()方法和Object类中的equals()方法保持一致
- 也就是地址不同的两个对象,那么这两个对象的哈希码值也不同
重写equals(Object o)方法时的基本原则:
-
重写equals方法的时候要注意一定要将我们的参数对象一定要考虑为空的情况
- 这里如果我们没有将我们的参数对象为空的情况排除在外,那么这个时候我们就可能会去用这个空的对象去调用一个数据进行比较,这个时候就会出现空指针异常
eg:
package 面向对象练习;
import java.util.Objects;
public class Cat {
private String name;
private int age;
public Cat(){
}
public Cat(String name,int age ){
this.name=name;
this.age=age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (getClass() != o.getClass()) return false;
/*
这个时候只是排除了我们的参数对象和模板类的类型和我们的Cat类型不一致的情况,但是并没有排除空指针的可能,这个时候
如果参数对象为null,那么这个时候就会出现空指针异常
*/
Cat cat = (Cat) o;
return age == cat.age &&
Objects.equals(name, cat.name);
}
}
class Test{
public static void main(String[] args) {
Cat cat = new Cat(“小橘”,3);
cat.equals(null);
}
}
2. 重写equals()方法一定要考虑参数对象类型和我们的本类(也就是调用类)不一致的情况
* 如果不一致就要排除,如果参数对象和我们的本类的类型都不一致,那么我们的向下转型时就会出现一个类型转换异常(ClassCastException)
eg:
```java
package equals方法;
import java.util.Objects;
public class Cat2 {
private String name;
private int age;
public Cat2() {
}
public Cat2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o==null) return false;
/*
这个时候我们只是将我们参数为空时出现空指针的情况排除在外了,这个时候如果我们的本类对象调用equals()方法和
其他类的对象进行比较时就会出现类型转换异常(ClassCastException)
*/
Cat2 cat = (Cat2) o;
return age == cat.age &&
Objects.equals(name, cat.name);
}
}
class Test2{
public static void main(String[] args) {
Cat2 cat = new Cat2("小橘",3);
cat.equals(null);
}
}
- 最后就是我们重写equals()方法的时候一定要先从特殊情况出发,先排除特殊情况,这样可以提高程序的执行效率
重写hashCode()方法的基本原则:
- 在程序运行时,同一对象多次调用hashCode()方法,应该返回相同的值
- 当两个对象的equals()方法比较返回true时,则两个对象的hashCode()方法的返回值也相等
- 对象中作equals()方法比较的Field(字段,属性),都应该用来计算hashCode()的返回值
eg:这里我们举一个重写了equals()方法并且重写了hashCode()方法并且保持一致性的例子
package com.ffyc.javaoop.day19;
import java.util.Objects;
public class Dog {
private String name;
private int age;
public Dog(){
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog dog = (Dog) o;
return age == dog.age &&
Objects.equals(name, dog.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
- 在这里我们重写equals()方法时通过name属性和age属性判断,我们的hashCode()方法也是通过name属性和age属性进行计算
- 这样就可以保证我们的equals()方法和hashCode()方法保持一致
我们可以发现我们使用集成开发工具(比如使用IDEA或者使用eclipse生成的hashCode()方法中都有一个31我们的int类型的数值),这里为什么是31,这个31是怎么来的?
- 我们选择系数的时候要选择尽量大的系数,因为如果计算出来的hash地址越大,那么冲突的概率就会越小,使用hashCode()对散列结构的中的元素进行查找时的效率也会提高
- 并且我们的31只用5个bit(位)进行存储,做了乘法运算之后出现造成数据溢出的概率也比较小
- 并且31可以使用2<<5-1进行表示,我们使用位运算进行运算的效率要高,这个时候也就提高了hashCode()方法的使用效率
- 并且31是一个素数,使用素数去乘的好处是我们使用素数乘以一个数,那么最终计算出来的结果只能被这个素数和另一个乘数来整除(这里我们没有算计算的结果和1),这样也可以减小冲突的概率