Set针对复杂对象去重问题

Set针对复杂对象去重问题

​ 在项目中我们经常使用set,因其可以去重特性,平时使用较多的是基础数据类型,Set<Integer>, Set<Long>等,这些在使用中都没碰到什么问题。最近在项目中碰到自定义对象去重,用后

创建的对象去覆盖set中type相同的对象,于是想到Set这个集合类型,并且重写了自定义对象的equals()和hashCode()方法,但调试阶段发现结果并非所想。

​ 以下代码是自定义的Bean:

1@AllArgsConstructor 2@ToString 3public class Foo implements Serializable { 4 5 private static final long serialVersionUID = -3968061057233768716L; 6 @Getter 7 @Setter 8 private int type; 9 10 @Getter 11 @Setter 12 private String name; 13 14 @Override 15 public boolean equals(Object o) { 16 if (this == o) { 17 return true; 18 } 19 if (o == null || getClass() != o.getClass()) { 20 return false; 21 } 22 Foo foo = (Foo) o; 23 return type == foo.type; 24 } 25 26 @Override 27 public int hashCode() { 28 return Objects.hash(type); 29 } 30} 31

​ 接下来写个单元测试吧

1public class SetTest { 2 3 @Test 4 public void test1() { 5 Set<Foo> set = new HashSet<>(); 6 Foo foo = new Foo(1, "1"); 7 set.add(foo); 8 foo = new Foo(2, "2"); 9 set.add(foo); 10 foo = new Foo(1, "3"); 11 set.add(foo); 12 // 输出结果是什么 13 System.out.println("set = " + set); 14 15 } 16 17

​ 第一感觉会输出什么,set.size()=2是毫无疑问的,重点是type=1的对象对应的name是1还是3?当时我用脑子意想出来的name=3。可能会有童鞋认为输出的结果name=1,恭喜你答对了,确实

是name=1。

1set = [Foo(type=1, name=1), Foo(type=2, name=2)] 2

​ 为什么不是3呢,3呢,3呢?接下来我们分析下,我们都知道当我们向HashMap中放入相同key和不同val放入到map中,map会保留最新的val(hashMap源码分析网上有很多解析,这里不再赘

述了,自行Google下),可见map其实也是保证key唯一的,不会出现key对应多个val的值(限:jdk中自带HashMap,Guava中Multimap是可以key对应多个val的,get(key)时返回的是val的集

合),HashSet就是利用HashMap中key唯一不重复的特性来实现的。

​ jdk1.8源码

1public class HashSet<E> 2 extends AbstractSet<E> 3 implements Set<E>, Cloneable, java.io.Serializable 4{ 5 static final long serialVersionUID = -5024744406713321676L; 6 7 private transient HashMap<E,Object> map; 8 9 // Dummy value to associate with an Object in the backing Map 10 private static final Object PRESENT = new Object(); 11 12 /** 13 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has 14 * default initial capacity (16) and load factor (0.75). 15 */ 16 public HashSet() { 17 map = new HashMap<>(); 18 } 19 20 -----------add部分 begin-------------------- 21 22 /** 23 * Adds the specified element to this set if it is not already present. 24 * More formally, adds the specified element <tt>e</tt> to this set if 25 * this set contains no element <tt>e2</tt> such that 26 * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>. 27 * If this set already contains the element, the call leaves the set 28 * unchanged and returns <tt>false</tt>. 29 * 30 * @param e element to be added to this set 31 * @return <tt>true</tt> if this set did not already contain the specified 32 * element 33 */ 34 // 其实这里是有返回值的 35 public boolean add(E e) { 36 return map.put(e, PRESENT)==null; 37 } 38 -----------add部分 end-------------------- 39 40 41 42调用add()实际就是调用HashMap的put方法,注意此时put的val是固定式<font color="red">**PRESENT**</font>是final修饰的一个对象,针对所有key都一样,因为我们是set所有val对我们来说没啥用。但是我们注意到其 43

实add()方法是有返回值的,当调用map.put()方法返回的对象是null的时候则代表新增成功,set中以前不存在此元素。当返回的对象not null的时候代表set中有此对象,而我们关注的其实只是key,

并不关注这个val。

1 public V put(K key, V value) { 2 return putVal(hash(key), key, value, false, true); 3 } 4 5----------- 6 7 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 8 boolean evict) { 9 Node<K,V>[] tab; Node<K,V> p; int n, i; 10 if ((tab = table) == null || (n = tab.length) == 0) 11 n = (tab = resize()).length; 12 if ((p = tab[i = (n - 1) & hash]) == null) 13 tab[i] = newNode(hash, key, value, null); 14 else { 15 Node<K,V> e; K k; 16 // 重点在这里 17 if (p.hash == hash && 18 ((k = p.key) == key || (key != null && key.equals(k)))) 19 e = p; 20 else if (p instanceof TreeNode) 21 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 22 else { 23 for (int binCount = 0; ; ++binCount) { 24 if ((e = p.next) == null) { 25 p.next = newNode(hash, key, value, null); 26 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 27 treeifyBin(tab, hash); 28 break; 29 } 30 if (e.hash == hash && 31 ((k = e.key) == key || (key != null && key.equals(k)))) 32 break; 33 p = e; 34 } 35 } 36 37 // 当我们第二次加入type=1时代码肯定就走到了这里,return oldValue其实也就是returun我们传入的PRESENT not nul 38 if (e != null) { // existing mapping for key 39 V oldValue = e.value; 40 if (!onlyIfAbsent || oldValue == null) 41 e.value = value; 42 afterNodeAccess(e); 43 return oldValue; 44 } 45 } 46 ++modCount; 47 if (++size > threshold) 48 resize(); 49 afterNodeInsertion(evict); 50 return null; 51} 52

​ 重点在这里

​ 重点在这里

​ 重点在这里

1if (p.hash == hash && 2 ((k = p.key) == key || (key != null && key.equals(k)))) 3 e = p; 4

​ hash相等是肯定的,因为我们重写了Foo对象的hashCode方法,只跟type有关,所有p.hash==hash为true,大括号中(k = p.key) == key部门为false因为对象地址不同,但是

key != null && key.equals(k)是为true的,因为重写了equals()方法也只跟type有关,都为1,所以e = p了,流程判断语句结束,return一个not null的value。其实putVal()流程控制来看,只要我们的key

是存在的都会返回一个not null的val,而新插入的key返回值都是null。

​ 代码调试截图走一波

最后没办法先remove()后add()。。。

注:代码中使用了lombok插件,非常好用哈哈,墙裂推荐。一个@Data搞定所有,不要告诉我idea代码自动生成,想一下,当你开发代码时给对象增加一个属性是不是还要给新属性生成get和set方法,还要toString()是不是也要改,而你加了@Data属性,这些都不需要考虑了。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Redis都有哪些数据类型

string这是最基本的类型了,就是普通的set和get,做简单的kv缓存hash这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以操作hash里的某个字段。key150value{“id”:

oracle游标的例子

declare    cursor ca is select id_no, name from user where ym201401;begin    for cb in ca loop        update path set enamecb.name where id_nocb.id

List stream 对象 属性去重

单值去重不写了,记录对象去重随手一个对象:@Data@AllArgsConstructorpublicclassMilk{privateIntegerkey;privateStringvalue;}操作:packagecom.yus.util;