为什么HashMap/HashSet需要重写equals方法和hashCode方法

引言

在学习集合中,HashMap/HashSet是非常常用的集合,但是在使用时刻要想着重写equals方法和hashCode方法,所以在这里简单说一说在使用HashMap或者HashSet中为什么必须要重写equals方法和hashCode方法,

在介绍之前先简单介绍一下一种数据结构——哈希表,如果不理解这个数据结构,那么不利于对重写原因的理解的;

哈希表

基本思想: 记录的存储位置关键字之间存在对应关系,Loc(i)=H(keyi) (注:这是一个哈希函数)

优点: 查找速度极快O(1),查找效率与元素个数n无关

注:略去部分内容,只看本文需要的内容;
哈希表的一种存储方法(处理冲突的方法)叫做:链地址法,下面也围绕这个方法展开的

基本思想相同哈希地址的记录链成一单链表,m个哈希地址就设m个单链表,然后用用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构;(一定要理解这句话!!!)
在这里插入图片描述
就像这样子,每一个哈希地址对应一个单链表,每个单链表上的每个key值对应一个value值;所以看哈希表就像一个数组和多个链表的合体;

先有个大致印象,下面我们看看HashMap的结构
!]
解释一下:哈希地址就类似数组下标,在图中用hash表示,HashMap中的key值就是k,Vaule值就是v;

当我们往HashMap中存储数据时,先通过hash确定到某一条链表上,然后再通过key值在链表中进行比较,如果没有相同的key值的话,就在最后新创建一个节点把Value值v放进去,如果有相等的key值,那么新的v会覆盖之前的v

那么hash值是从哪来的呢?
hash值就是key的hashCode的返回值;
有了hash值后key值比较的过程就是使用equals方法;

能看出来为什么要重写hashCode方法和equals方法了吗?
就是因为每一个对象的hashCode值都不相同,而不同的对象使用equals方法比较的只是内存地址,而内存地址也不相同;
我们想要的结果只是 相同内容的hashCode值相同,相同内容的比较相同!
所以我们要针对我们的对象内容重写hashCode方法和equals方法;

下面通过例子更形象的说明:

实例说明

这里我自定义了一个User类,并没有重写HashCode和equals;
我们先看看有相同内容的对象的HashCode值是否相同:

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class MapTest05 {
    public static void main(String[] args) {
        Map<User, String> map = new HashMap<>();
        // 创建四个User对象
        User u1 = new User("张三", 18);
        User u2 = new User("李四", 20);
        User u3 = new User("王五", 30);
        User u4 = new User("王五", 30); // 创建一个和u3内容一样的对象u4

        // 先看看hashCode值
        System.out.println("u1的hashCode值为:" + u1.hashCode());
        System.out.println("u2的hashCode值为:" + u2.hashCode());
        System.out.println("u3的hashCode值为:" + u3.hashCode());
        System.out.println("u4的hashCode值为:" + u4.hashCode());
    }
}
// 用户类
class User {
    private String name; // 姓名
    private int age; // 年龄

    // 默认构造
    User() {}
    // 有参构造
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // equals方法未重写
    // hashcode方法未重写
}

输出结果:

u1的hashCode值为:2003749087
u2的hashCode值为:1480010240
u3的hashCode值为:81628611
u4的hashCode值为:1828972342

可以看到,每一个对象的hashCode值都不同,尤其注意u3和u4内容相同而hashCode值不同;
接下来把它们放到map中看看会怎样;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapTest05 {
    public static void main(String[] args) {
        Map<User, String> map = new HashMap<>();
        // 创建四个User对象
        User u1 = new User("张三", 18);
        User u2 = new User("李四", 20);
        User u3 = new User("王五", 30);
        User u4 = new User("王五", 30); // 创建一个和u3内容一样的对象u4
        // 将User作为索引放入map中
        map.put(u1, "是个小帅哥");
        map.put(u2, "是个大帅哥");
        map.put(u3, "是个老帅哥");
        map.put(u4, "是个帅哥");
        // 输出map
        Set<Map.Entry<User, String>> set = map.entrySet();
        for (Map.Entry<User, String> i : set) {
            System.out.println(i.getKey() + " = " + i.getValue());
        }
    }
}
// 用户类
class User {
    private String name; // 姓名
    private int age; // 年龄

    // 默认构造
    User() {}
    // 有参构造
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // equals方法未重写
    // hashcode方法未重写
}

输出结果:

map.User@776ec8df = 是个小帅哥
map.User@41629346 = 是个帅哥
map.User@4eec7777 = 是个大帅哥
map.User@3b07d329 = 是个老帅哥

可以看到,u3和u4内容相同,但是放到HashMap中后u4并没有把u3覆盖,这样子是万万不行的;我们都知道:
哈希表中一个索引key对应的只能是一个value值,而在这里u3和u4的索引按道理来说都相同,但是却对应不同的value值;
这就是因为我们没有重写hashCode,我们认为的索引是u3、u4的内容,而实际情况下的索引是它们不同的hashCode值,所以就会造成这种情况的发生;

如果重写了hashCode和equals方法呢?我们还是先看看重写后hashCode值;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class MapTest05 {
    public static void main(String[] args) {
        Map<User, String> map = new HashMap<>();
        // 创建四个User对象
        User u1 = new User("张三", 18);
        User u2 = new User("李四", 20);
        User u3 = new User("王五", 30);
        User u4 = new User("王五", 30); // 创建一个和u3内容一样的对象u4

        // 先看看hashCode值
        System.out.println("u1的hashCode值为:" + u1.hashCode());
        System.out.println("u2的hashCode值为:" + u2.hashCode());
        System.out.println("u3的hashCode值为:" + u3.hashCode());
        System.out.println("u4的hashCode值为:" + u4.hashCode());
    }
}
// 用户类
class User {
    private String name; // 姓名
    private int age; // 年龄

    // 默认构造
    User() {}
    // 有参构造
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 重写equals方法
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return age == user.age && name.equals(user.name);
    }
    // 重写hashCode方法
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

输出结果

u1的hashCode值为:24022538
u2的hashCode值为:26104872
u3的hashCode值为:29050006
u4的hashCode值为:29050006

和没有重写的差别一眼就能看出来了,尤其是u3和u4,这次因为它们的对象内容是相同的,所以它们的hashCode也是相同的;
那么这样子的话把它们放到HashMap中会怎样?看看下面代码:

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class MapTest05 {
    public static void main(String[] args) {
        Map<User, String> map = new HashMap<>();
        // 创建四个User对象
        User u1 = new User("张三", 18);
        User u2 = new User("李四", 20);
        User u3 = new User("王五", 30);
        User u4 = new User("王五", 30); // 创建一个和u3内容一样的对象u4
        // 将User作为索引放入map中
        map.put(u1, "是个小帅哥");
        map.put(u2, "是个大帅哥");
        map.put(u3, "是个老帅哥");
        map.put(u4, "是个帅哥");
        // 输出map
        Set<Map.Entry<User, String>> set = map.entrySet();
        for (Map.Entry<User, String> i : set) {
            System.out.println(i.getKey() + " = " + i.getValue());
        }
    }
}
// 用户类
class User {
    private String name; // 姓名
    private int age; // 年龄

    // 默认构造
    User() {}
    // 有参构造
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 重写equals方法
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return age == user.age && name.equals(user.name);
    }
    // 重写hashCode方法
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

输出结果:

map.User@16e8e0a = 是个小帅哥
map.User@18e5428 = 是个大帅哥
map.User@1bb4496 = 是个帅哥

可以看到输出结果变成了三个,而且因为u3和u4的内容相同,所以最后的u4的value覆盖了u3,这就实现了我们的需求:通过索引对象的内容进行比较;

ps:如果我的语言描述有问题的话可以自己试试这几个代码,体会一下我所想要描述的意思,这一块我可能说的有点啰嗦,就有点乱,自己敲代码试试就会明白我说的是什么了;

关于HashSet

其实HashSet和HashMap是一样的,在HashSet底层调用了HashMap,底层代码如图:
在这里插入图片描述
所以记住一点就行了:HashMap需要注意的HashSet也需要注意

总结

说了这么多,其实就是想要说一句话:
放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法

这也是这篇文章讨论的核心,所以在写代码的时候一定要记住;如果没有看懂的话那就记住这句话就行了!

对了还要注意一点:包装类和String类的hashCode方法和equals方法SUN公司都已经重写过了,所以不需要重写了!!
可以自己写个代码验证一下,这里就不举例了;

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YXXYX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值