Java中的Set对象去重
前言部分
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对象去重的更多相关文章
- java中对集合对象list的几种循环访问
java中对集合对象list的几种循环访问的总结如下 1 经典的for循环 public static void main(String[] args) { List<String> li ...
- Java中的函数对象
初次听说java中的函数对象可能,比较的陌生.可以类比着来理解一下,人们常说java中没有了指针,殊不知,java中的对象引用就是指针,有时候我们说一个对象往往指的就是这个对象的引用,也就是说基本上把 ...
- (转)java中对集合对象list的几种循环访问总结
Java集合的Stack.Queue.Map的遍历 在集合操作中,常常离不开对集合的遍历,对集合遍历一般来说一个foreach就搞定了,但是,对于Stack.Queue.Map类型的遍历,还是有一 ...
- Java中创建实例化对象的几种方式
Java中创建实例化对象有哪些方式? ①最常见的创建对象方法,使用new语句创建一个对象.②通过工厂方法返回对象,例:String s =String.valueOf().(工厂方法涉及到框架)③动用 ...
- Java中字节与对象之间的转换
近期公司里面用到了消息队列,而正如我们知道的是消息队列之间的是通过二进制形式的.以下就分享一下java中字节与对象之间的转换. 主要是用到了ByteArrayOutputStream和ObjectOu ...
- java中的string对象深入了解
这里来对Java中的String对象做一个稍微深入的了解. Java对象实现的演进 String对象是Java中使用最频繁的对象之一,所以Java开发者们也在不断地对String对象的实现进行优化,以 ...
- Java中创建的对象多了,必然影响内存和性能
1, Java中创建的对象多了,必然影响内存和性能,所以对象的创建越少越好,最后还要记得销毁.
- 利用reduce方法,对数组中的json对象去重
数组中的json对象去重 var arr = [{ "name": "ZYTX", "age": "Y13xG_4wQnOWK1Q ...
- Java中list<Object>集合去重实例
一:Java中list去重的方法很多,下面说一下其中一种方法:把list里的对象遍历一遍,用list.contain(),如果不存在就放入到另外一个list集合中: 二:实例 这里需要注意的是:使用c ...
随机推荐
- ant-design-vue 快速避坑指南
ant-design-vue是蚂蚁金服 Ant Design 官方唯一推荐的Vue版UI组件库,它其实是Ant Design的Vue实现,组件的风格与Ant Design保持同步,组件的html结构和 ...
- 【QT】QtConcurrent::run()+QThreadPool实现多线程
往期链接: <QThread源码浅析> <子类化QThread实现多线程> <子类化QObject+moveToThread实现多线程> <继承QRunnab ...
- keras中的early stopping
目的:防止过拟合 # early stoppping from keras.callbacks import EarlyStopping early_stopping = EarlyStopping( ...
- 像用excel一样用pandas
1 说明 预计需要15min阅读此教材: 本教材仅讲述如何查看excel数据,筛选,排序,查找替换数据,不涉及excel中复杂的图形绘制及样式修改. 2 假设 假设,已经存在一个test.xlsx文件 ...
- HTML/css清除浮动的几种方式
浮动在HTML/CSS布局中十分常见,虽然浮动布局可以实现很多有用的排版效果,但是浮动的元素脱离了文档流,可能造成包含块高度塌陷.下方行框上移等行为.因此在使用浮动实现想要的效果后,通常还需要清除浮动 ...
- 《.NET 5.0 背锅案》第4集:一个.NET,两手准备,一个issue,加倍关注
第1集:验证 .NET 5.0 正式版 docker 镜像问题 第2集:码中的小窟窿,背后的大坑,发现重要嫌犯 EnyimMemcachedCore 第3集-剧情反转:EnyimMemcachedCo ...
- Spring源码之FactoryBean的实现
https://zhuanlan.zhihu.com/p/97005407 https://blog.csdn.net/qq_35634181/article/details/104507465 总结 ...
- DP中环形处理 +(POJ 1179 题解)
DP中环形处理 对于DP中存在环的情况,大致有两种处理的方法: 对于很多的区间DP来说,很常见的方法就是把原来的环从任意两点断开(注意并不是直接删掉这条边),在复制一条一模一样的链在这条链的后方,当做 ...
- C++ const的自我理解
C++学习笔记–const const 是 constant 的缩写,本意是不变的,不易改变的意思.在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数. C++ const ...
- 应届生应聘阿里,腾讯,美团90%会被问到的Netty面试题!史上最全系列!
1.BIO.NIO 和 AIO 的区别? BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理.线程开销大.伪异步 IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源 ...