前言部分

Set<T> 去重相信大家一定不陌生,尤其是在 Set<String>Set<Integer> 等等,但是在使用 Set<实体> ,在不重写 equals()、hashCode() 方法情况下,直接使用貌似并不能生效。

所以想要 Set<实体> 实现去重,核心部分在实体中重写 equals()、hashCode() 方法。

如下以 User 实体为例,进行测试。

代码部分

测试代码:

public static void main(String[] args) {
   Set<User> userSet = new HashSet<User>(){{
       add(new User("张三",10));
       add(new User("张三",20));
       add(new User("张三",10));
   }};
    userSet.forEach(user -> {
        System.out.println(String.format("name: %s, age:%s",user.getName(),user.getAge()));
    });
}

打印结果:

name: 张三, age:20
name: 张三, age:10

实体对象(User.java): 重写了 equals()、hashCodd() 方法。

public class User {

    public User(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    /** 姓名 **/
    private String name;

    /** 年龄 **/
    private Integer age;

    省略get、set方法...

    /**
     * 重写equals方法,如果对象类型是User,先比较hashcode,一致的场合再比较每个属性的值
     */
    @Override
    public boolean equals(Object obj) {
        System.out.println("调用equals方法,当前的hashCode为:"+hashCode());
        /** 对象是 null 直接返回 false **/
        if (obj == null) {
            return false;
        }
        /** 对象是当前对象,直接返回 true **/
        if (this == obj) {
            return true;
        }
        /** 判断对象类型是否是User **/
        if (obj instanceof User) {
            User vo = (User) obj;
            /** 比较每个属性的值一致时才返回true **/
            /** 有几个对象就要比较几个属性 **/
            if (vo.name.equals(this.name) && vo.age.equals(this.age)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 重写hashcode方法,返回的hashCode一样才再去比较每个属性的值
     */
    @Override
    public int hashCode() {
        return this.getName().hashCode() * this.getAge().hashCode();
    }

}

解释部分

为什么 Set<String>Set<Integer> 就可以直接实现去重,而 Set<实体> 就不可以,反而要重写 equals()、hashCode() 方法才能实现,更甚者是,只重写 equals() 方法,而不重写 hashCode() 方法都没法完成去重~

大家对这个问题有过疑惑吗?

1、HashSet 添加数据过程

HashSet 的底层实现,相信大家都清楚是 HashMap 吧?我们在 add() 数据时,其实一层层找,最终是调的 HashMap 的 put() 方法,如下是 HashSet 的 add() 方法,其中 map 为 HashMap。

我们再点一层找到 HashMap 的 put() 方法:

如上图所示,通过 putVal() 方法我们大致有了个概念了,判断是否为旧值就是对 hash 值、key 值进行比较。

hash 值比较自然调用的事 hashCode() 方法,而 key 值的比较实用的是 equals() 方法。

了解到这基本就可以看出 hashCode() 、equals() 方法对于去重的重要性了。

2、Set<单属性> 可以直接使用去重

那么接下来我们就可以来看看 Set<单属性>(单属性:String、Integer等),为什么直接使用就可以去重了。

我们以 String 为例,假设有两个字符串 a、b,如下:

String a = "123";
String b = "123";
System.out.println("a.hashCode:"+a.hashCode());
System.out.println("b.hashCode:"+b.hashCode());
System.out.println(a.equals(b));

打印结果如下:

a.hashCode:48690
b.hashCode:48690
true

很显然,在没有重写 hashCode() 、equals() 方法时,字符串 a、b 的 hashCode,equalse() 是一致的,那么这两个就可以视为一个对象,所以用在 Set 里面就可以直接去重。

但是为什么会一致呢?

任何对象在不重写 equals()、hashcode() 的情况下,使用的是 Object 对象的 equals() 方法和 hashcode() 方法,而重点就是,默认的 equals() 方法判断的是两个对象的引用指向的是不是同一个对象;而 hashcode 也是根据对象地址生成一个整数数值;

显然字符串 a、b 这两个条件都满足,所以对于 Set 来说就是一个对象的概念。

3、Set<实体> 去重

但是换到对于实体对象就行不通了,我们再来套 Object 的 equals()、hashCode() 方法。

当我们 new User() 对象时,两个对象的地址引用肯定是不同的;其次 hashcode 是根据对象地址生成的,这样显然也不同,所以对于 Set 来说,那么去重就行不通。

因此,想要让 Set<实体> 实现去重效果,那么就需要重写 equals() 、hashCode() 方法。

只有两个对象的 hashCode() 方法的值一致,且 equalse() 方法返回 true,那么这对于 Set<实体> 来说就可以看做一个对象, 如果两者只满足一个是不可以的(只重写一个),举个例子:

equales()重写,hashCode()不重写

@Override
public boolean equals(Object obj) {
    return true;
}

//@Override
//public int hashCode() {
//    return this.getName().hashCode() * this.getAge().hashCode();
//}

执行代码:

Set<User> userSet = new HashSet<User>(){{
   add(new User("张三",10));
   add(new User("张三",20));
   add(new User("张三",10));
}};

userSet.forEach(user -> {
    System.out.println(String.format("name: %s, age:%s",user.getName(),user.getAge()));
});

打印内容:

name: 张三, age:10
name: 张三, age:10

equales()不重写,hashCode()重写

//@Override
//public boolean equals(Object obj) {
//    return true;
//}

@Override
public int hashCode() {
  return this.getName().hashCode() * this.getAge().hashCode();
}

执行代码+打印内容如上:

name: 张三, age:10
name: 张三, age:10

总结

总之,要想保证 Set<实体> 实现去重,就需要两个实体 “一致”,这里的一致是只需要满足如下两个条件:

  • 重写 hashCode() 方法,确保两者 hashcode 一致,比如使用属性相乘或者相加。
  • 重写 equals() 方法,相同对象、属性值相同对象皆为相等。

通过上面这些例子也能看出重写 equals 方法,就必须重写 hashCode 的重要性,因为只重写 equals() 不一定能满足预期相等的效果。

如下是阿里巴巴开发手册,关于 hashCode 和 equals 的处理规则:

希望这篇文章对你有所帮助。博客园持续更新,欢迎关注。

博客园:https://www.cnblogs.com/niceyoo

Java中的Set对象去重的更多相关文章

  1. java中对集合对象list的几种循环访问

    java中对集合对象list的几种循环访问的总结如下 1 经典的for循环 public static void main(String[] args) { List<String> li ...

  2. Java中的函数对象

    初次听说java中的函数对象可能,比较的陌生.可以类比着来理解一下,人们常说java中没有了指针,殊不知,java中的对象引用就是指针,有时候我们说一个对象往往指的就是这个对象的引用,也就是说基本上把 ...

  3. (转)java中对集合对象list的几种循环访问总结

    Java集合的Stack.Queue.Map的遍历   在集合操作中,常常离不开对集合的遍历,对集合遍历一般来说一个foreach就搞定了,但是,对于Stack.Queue.Map类型的遍历,还是有一 ...

  4. Java中创建实例化对象的几种方式

    Java中创建实例化对象有哪些方式? ①最常见的创建对象方法,使用new语句创建一个对象.②通过工厂方法返回对象,例:String s =String.valueOf().(工厂方法涉及到框架)③动用 ...

  5. Java中字节与对象之间的转换

    近期公司里面用到了消息队列,而正如我们知道的是消息队列之间的是通过二进制形式的.以下就分享一下java中字节与对象之间的转换. 主要是用到了ByteArrayOutputStream和ObjectOu ...

  6. java中的string对象深入了解

    这里来对Java中的String对象做一个稍微深入的了解. Java对象实现的演进 String对象是Java中使用最频繁的对象之一,所以Java开发者们也在不断地对String对象的实现进行优化,以 ...

  7. Java中创建的对象多了,必然影响内存和性能

    1, Java中创建的对象多了,必然影响内存和性能,所以对象的创建越少越好,最后还要记得销毁.

  8. 利用reduce方法,对数组中的json对象去重

    数组中的json对象去重 var arr = [{ "name": "ZYTX", "age": "Y13xG_4wQnOWK1Q ...

  9. Java中list<Object>集合去重实例

    一:Java中list去重的方法很多,下面说一下其中一种方法:把list里的对象遍历一遍,用list.contain(),如果不存在就放入到另外一个list集合中: 二:实例 这里需要注意的是:使用c ...

随机推荐

  1. python面向对象(类与对象)

    面向对象思想 关注公众号"轻松学编程"了解更多. 1.面向对象的设计思想 面向对象是基于万物皆对象这个哲学观点. 2.面向对象和面向过程的区别 面向过程 在生活中: 它是一种看待问 ...

  2. CF1413C Perform Easily 题解

    毒瘤C题,考场卡我1个小时 首先,这道题难点在哪里?它的最大值与最小值都是浮动的. 怎么办?把最小/最大值固定! 以把最小值固定为例,我们枚举每个音符,并枚举它使用哪条琴弦,将它此时的位置强制其作为最 ...

  3. Java入门(5)

    阅读书目:Java入门经典(第7版) 作者:罗格斯·卡登海德 protected变量只能在其所在的类,该类的子类,以及同一个包里的其他类中使用.包是一组用于完成相同目标的相关类. private变量只 ...

  4. 使用 Xunit.DependencyInjection 改造测试项目

    使用 Xunit.DependencyInjection 改造测试项目 Intro 这篇文章拖了很长时间没写,之前也有介绍过 Xunit.DependencyInjection 这个项目,这个项目是由 ...

  5. 使用docker 部署codis

    使用docker 部署codis 原文地址:https://www.jianshu.com/p/85e72ae6fec3 codis的架构图 1.zookeeeper,用于存放统一配置信息和集群状态 ...

  6. 阿里巴巴开发手册强制使用SLF4J作为门面担当的秘密,我搞清楚了

    之前已经详细.全面地介绍了 Log4j,相信小伙伴们已经完全掌握了.那我在读嵩山版的阿里巴巴开发手册(没有的小伙伴,记着找我要)的时候,就发现了一条「强制」性质的日志规约: 应用中不可以直接使用日志系 ...

  7. Linux sar命令参数详解

    转载自http://www.chinaz.com/server/2013/0401/297942.shtml sar(System Activity Reporter系统活动情况报告)是目前 Linu ...

  8. OpenCV计算机视觉学习(11)——图像空间几何变换(图像缩放,图像旋转,图像翻转,图像平移,仿射变换,镜像变换)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 图像 ...

  9. fork 子进程,父进程对于变量的共享

    经过代码的练习发现: fork创建的子进程会完全复制父进程的代码包括变量,既复制fork之前创建的变量. 但是在创建子进程后,子进程与父进程对同一个变量的改变将相互不受影响,即使获取同一变量的地址是一 ...

  10. netfilter 的扩展功能 helper tftp-nat

    /* 需要对conntrack进行功能扩展的协议,会初始化一个struct nf_conntrack_helper 实例,把该实例注册到Netfilter中管理的全局哈希表中. 查找helper使用的 ...