重写equal和hashCode方法,用集合去重对象
综述:
- equal和hasCode方法讲解
- 重写equal和hasCode,用HashSet集合进行对象去重
- 自我理解
equal和hasCode
- equal和hasCode都是Object中的方法,所有的类都有这这两种方法。
先看代码,在讲解
user实体类
/**
* 根据用户的firstName和lastName确定是否是这个用户
*/
public class User {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
public class EqualDemo {
public static void main(String[] args) {
/**
* step1 基本类型比较:所有基本类型直接比较的是值
*/
Integer i1 = 1;
Integer i2 = 1;
System.out.print("基本类型Integer比较......: == —>");
System.out.print(i1.equals(i2));
System.out.print(" equal —>");
System.out.println(i1.equals(i2));
/**
* step2 引用类型比较:一种比较String类型,一种比较User对象类型
*/
String s1 = "abc";
String s2 = new String("abc");
String s3 = "abc";
System.out.print("引用类型String比较......: == —>");
System.out.print(s1 == s2);
System.out.print(" s1和s3 —>");
System.out.print(s1 == s3);
System.out.print(" equal —>");
System.out.println(s1.equals(s2));
User u1 = new User();
u1.setFirstName("wang");
u1.setLastName("dan");
User u2 = new User();
u2.setFirstName("wang");
u2.setLastName("dan");
System.out.print("引用类型User对象比较......: == —>");
System.out.print(u1 == u2);
System.out.print(" equal —>");
System.out.println(u1.equals(u2));
}
结果
基本类型Integer比较......: == —>true equal —>true
引用类型String比较......: == —>false s1和s3 —>true equal —>true
引用类型User对象比较......: == —>false equal —>false
- 详谈
1)基本类型包括八种,每一种基本类型都重写了Object中的equal方法,基本类型在JVM中引用直接指向的就是当前基本类型的值(见下图),所以基本类型中equal方法和==都是值比较。
2)引用类型在JVM中,栈中的引用指向的是堆内存放值的地址,然后可以根据地址取出值进行比较。String类型’==’不相等是因为引用s1和s2指向的地址不相同,所以他们不相等,但是s1和s3相等,因为s1和s3指向的地址相同,变量s1和s3初始化时,首先会检查方法区中常量中是否有’abc’这个值,如果存在会将s3的引用直接指向这个值,如果不存在就会在常量池中创建这个值,然后将变量引用指向这个值。String类型中’equal’方法比较特殊,因为在String中重写了这个方法,他比较的是引用指向的地址的值的大小。
3)User对象的比较的无论是equal还是==都是不相等的,因为他并没有直接比较的是对象指向的地址是否相同,u1和u2两个对象都是new出来的,所以他们的地址值肯定不相同。如果想用equal比较其他的值,需要在User对象内存重写equal方法。 - hasCode最常见的地方就是HashSet和HashMap,根据哈希求值将元素存储到集合中。
重写equal和hasCode
这个地方能墨迹一点,因为我想分别说明一下,觉得还是有必要的,耐心的看下去,你就会发现为什么要同时重写这两个方法。
实体代码OvriUser ,重写equalsh和hashCode方法,如下1,2点都是注释掉了没有重写的部分,会在重写hashCode方法的地方说一下hashCode方法。
public class OvriUser {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public boolean equals(Object o) {
OvriUser u = (OvriUser) o;
return this.firstName == u.getFirstName() && this.lastName == u.getLastName();
}
@Override
public int hashCode() {
int res = 7;
res = res + firstName.hashCode()+lastName.hashCode();
return res;
}
}
- 对象重equal方法,没有重写hashCode方法
结果:在地方部分展示所有比较的代码
重写equals方法......: == —>false equal —>true
没有重写hashCode Ovriuser:
356573597 1735600054
重写equals方法,在对象比较的时候,值会相同,因为在重写的equals方面里面会比较这两个值,没有重写的hashCode值是不相同的。
2. 对象重写hashCode方法,不重写equal方法
结果:在地方部分展示所有比较的代码
没有重写equals方法......: == —>false equal —>false
重写hashCode Ovriuser:
3741083 3741083
重写hashCode方法的结果,hashCode值是相同的,equals确实不相同的。hashCode方法就是自己制定一个规则,几个计算方法。
3. 对象重写equal和hashCode方法
全部重写的代码
public class OvriTest {
public static void main(String[] args) {
OvriUser o1 = new OvriUser();
o1.setFirstName("wang");
o1.setLastName("dan");
OvriUser o2 = new OvriUser();
o2.setFirstName("wang");
o2.setLastName("dan");
/**
* 重写equals方法,没有重写hashCode方法
*/
System.out.print("......: == —>");
System.out.print(o1 == o2);
System.out.print(" equal —>");
System.out.println(o1.equals(o2));
System.out.println("hashCode Ovriuser: ");
System.out.print(o1.hashCode());
System.out.print(" "+o2.hashCode());
/**
* 没有重写equals方法,重写hashCode方法
*/
System.out.print("equals方法......: == —>");
System.out.print(o1 == o2);
System.out.print(" equal —>");
System.out.println(o1.equals(o2));
System.out.println("重写hashCode Ovriuser: ");
System.out.print(o1.hashCode());
System.out.print(" "+o2.hashCode());
}
}
全部重写之后发现,equals和hashCode的值是相同的。
结果:
......: == —>false equal —>true
hashCode Ovriuser:
3741083 3741083
equals方法......: == —>false equal —>true
重写hashCode Ovriuser:
3741083 3741083
- 用HashSet进行对象去重
HashSet可以对重写了hashCode和equals方法的实体进行去重复。我们首先看下HashSet的数据结构:
(1)对于重写equals方法的实体(Ovriuser),他们的哈希值不相同,即num值不相同,在HashSet去重的时候计算相同实体的哈希值不同,所以不能够去重复。(上一步中的第一点)
(2)对于重写hasCode方法的实体,虽然能够得到相同的num值,但是他们在判断相等的时候结果是不相等。(上一步中的第二点)
(3)只有重写了equals和hasCode方法的才能用集合HashSet对实体(Ovriuser)进行去重。
重写equal和hasCode
其实HashSet本质上就是HashMap的键,如下为HashMap的数据结构:
(1)HashMap底层的实现就是一个线性数组+链表,HashMap和HashSet中hasCode方法作用都是一样的,就是求出哈希值,然后找到在哈希值在线性数组中的位置。equals方法对于HashSet来说就是重复用的,如果对象A、B的哈希值相同,equals值相同那么对象A、B就是重复对象,去掉一个即可。如果是HashMap中存在A、B、C三个对象的哈希值相同,equals值相同,那么他们存在哈希取值之后会出现冲突,链表的作用就是解决冲突的,当然链表的长度也不能够无线长,详情请了解哈希相关的,上图就是A、B、C在HashMap中的存储结构。
(2)HashMap都是通过put(key,value)对值进行存储的,相同哈希值的对象会以Map.Entry的形式存放到链表对应的位置。使用HashMap的get()方法时是如何找到对应的值得呢?同一个哈希对应着么多的对象,那么多的值,因为在链表的每个节点处存放的是一个节点Node