目录
为什么重写equals方法
基本数据类型的比较
首先得知道,对于基本数据类型的比较我们直接用双等符合(==)就能直接判断两个值是否相等。
- 代码理解
int a = 100;
int b = 200;
int c = 100;
System.out.println(a); //100
System.out.println(b); //200
System.out.println(c); //100
System.out.println(a == b); //false
System.out.println(a == c); //true
从中可以得到结论:
基本数据类型直接存储的是值,没有引用一说,即a与b、a与c的比较是直接用它们的值进行比较的,所以可以直接用 == 比较。
引用数据类型的比较
但java中除了基本数据类型,还有引用数据类型。引用数据类型包括常用的String和我们自定义的类等等。我们重写equals方法的目标也正是这些自定义的类。
-
难道自定义类就不可以直接用 == 比较了吗?答案是不行的
直接看代码(我们自定义一个Student类,给它name和age两个属性,它的set、get和有参构造方法就不粘出来了)
class Student{
private String name;
private Integer age;
}
我们先对没有重写equals()的Student类进行测试:
Student student1 = new Student("zhangsan", 20);
Student student2 = new Student("lisi", 20);
Student student3 = new Student("lisi", 20);
System.out.println(student1); //com.nbtech.dorm.Test$Student@490d6c15
System.out.println(student2); //com.nbtech.dorm.Test$Student@7d4793a8
System.out.println(student3); //com.nbtech.dorm.Test$Student@449b2d27
得出结论:
直接输出这些变量,得到的不是name或age,也不是name和age的任何一种组合形式,而是一条奇奇怪怪的东西,并且数据相同时这串东西也是不一样的。
可以大致分析这串东西知道,@前面的是该对象的所属类及包的位置,而@后面的则是地址,我们不需要知道这串东西怎么来的,只需要知道,引用数据类型的变量存的是一个地址,而不是具体的值,变量会通过这个地址在堆内存中找到指向的对象。
因此,直接用 == 不能比较两个对象是否相等一致,因为实际上只是在比较两条内存地址而已。所以,比较对象必须用equals方法(这里不要把equals()特殊化,实际上a方法b方法都可以,只有方法的内容是能比较的就可以)。
-
重写equals()的理由
理由都是源自于需求的,既然我们自定义了一些类,那么在调用的时候不免会出现对象与对象的比较,我们的预期是当对象间里面的属性值都相同时判定为true,或认为name值相同就判定对象相同也是可以的,这个看需求。 -
重写equals()
其实 equals方法重写的格式相对统一,像Idea是有统一的格式直接生成的,方法:在类中按ALT + Insert就可以看到。
对Student类直接生成重写的equals方法和对应解析如下:
@Override
public boolean equals(Object o) {
/*当前对象和传入对象的内存地址相同是直接相同返回true*/
if (this == o) return true;
/*当传入对象为null或不是同一个引用数据类型直接返回false*/
if (o == null || getClass() != o.getClass()) return false;
/*让对象之间的属性值进行一一比较,全相同时返回true,否则返回false*/
Student student = (Student) o;
return Objects.equals(name, student.name) &&
Objects.equals(age, student.age);
}
直接生成的equals方法的适用场景是所有属性值相同时才相等,这也适用于大部分场景。当然,需求不一样可以进行对应修改。
接下来,我们就可以用equals()进行对象间的比较了
Student student1 = new Student("lisi", 20);
Student student2 = new Student("zhangsan", 20);
Student student3 = new Student("lisi", 20);
System.out.println(student1.equals(student2)); //false
System.out.println(student1.equals(student3)); //true
符合预期结果。
为什么重写hashCode方法
刚开始可以有些人会疑惑:既然重写了equals方法,已经可以让我在对对象进行比较的时候得到预期结果了,重写hashCode方法还有什么必要呢?
确实,如果在你的项目中对象只需要在 i f 语句中使用,确实不需要重写hashCode方法,重写equals方法就能满足你的要求。(实际上这也得重写hashCode,原因是对象存储的内存消耗问题)
但有些场景是equals不能满足的,比如:当需要使用HashMap和HashSet的时候。
-
HashMap
首先得知道HashMap是以键值对的形式存储数据的,且键是唯一的。那么,当key是Student类型时,单单重写equals方法能符合这些规则吗?看代码:
//只重写equals(),不重写hashCode()
Student student = new Student("tom", 20);
Student student1 = new Student("tom", 20);
Map<Student,String> map = new HashMap();
map.put(student,"001");
map.put(student1,"002");
System.out.println(map.get(student)); //001
System.out.println(map.get(student1)); //002
System.out.println(map.size()); //2
我们可以看到,在同一个HashMap中成功放入了两个key值相同的student(当然,这是我们认为的而已,因为我们的定义是属性值相同就相同),这是不符合HashMap本身的规则的。
那为什么key规则上是唯一的又能同时插入呢?因为,HashMap底层是通过比较hashCode(哈希值)来插入的,即key值唯一值的是key的哈希值唯一。
看看它们的哈希值:
System.out.println(student.hashCode()); //1225616405
System.out.println(student1.hashCode()); //2101842856
不管获取几次哈希值都不会改变,算法是通过对象的属性值计算的一个固定值,也可以自己写一个算法。
重写hashCode()
Student student = new Student("tom", 20);
Student student1 = new Student("tom", 20);
Map<Student,String> map = new HashMap();
map.put(student,"001");
map.put(student1,"002");
System.out.println(map.get(student)); //002
System.out.println(map.get(student1)); //002
System.out.println(map.size()); //1
通过size()可以看到,这个HashMap只有1个元素,进一步通过key值get出它们对应的值,发现都是002,说明HashMap确立了对于Student对象的唯一性,后面插入的student1覆盖了前面的student,因此值为002。
再来看看它们的哈希值:
System.out.println(student.hashCode()); //3566787
System.out.println(student1.hashCode()); //3566787
确实都一样!
hashCode()是如何重写的
同样这里也是用idea直接生成的。生成的代码如下:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
这里是通过Objects调用hash方法并传入当前类所有属性值获得的哈希值,不能看明白具体的实现。
继续看看hash()怎么实现的,如下:
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
Object… values这是一个可变参数,传入的值会装进values这个数组中。
这里也不能看到具体的实现,继续一探究竟,如下:
public static int hashCode(Object a[]) {
//当数组为空时,直接返回0
if (a == null)
return 0;
int result = 1;
for (Object element : a)
//这个result是最终计算的哈希值,以31 * 1为基础值再加上数组中(即对象属性值)
//不为空的哈希值
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
好了,这是Arrays的一个计算哈希值的算法,是不是很简单,完全可以自己写一个。
哈希值其他的用处大家可以自己看看,这里不多说啦!本文为基础讲解,可以以此再深入研究。另外,文章可能存在不严谨或错误,望指出。