`
wx1568908808
  • 浏览: 31417 次
文章分类
社区版块
存档分类
最新评论

Set针对复杂对象去重问题

 
阅读更多

Set针对复杂对象去重问题

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

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

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

@AllArgsConstructor
@ToString
public class Foo implements Serializable {

    private static final long serialVersionUID = -3968061057233768716L;
    @Getter
    @Setter
    private int type;

    @Getter
    @Setter
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Foo foo = (Foo) o;
        return type == foo.type;
    }

    @Override
    public int hashCode() {
        return Objects.hash(type);
    }
}

​ 接下来写个单元测试吧

public class SetTest {

    @Test
    public void test1() {
        Set<Foo> set = new HashSet<>();
        Foo foo = new Foo(1, "1");
        set.add(foo);
        foo = new Foo(2, "2");
        set.add(foo);
        foo = new Foo(1, "3");
        set.add(foo);
        // 输出结果是什么
        System.out.println("set = " + set);

    }


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

是name=1。

set = [Foo(type=1, name=1), Foo(type=2, name=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源码

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
    
    -----------add部分 begin--------------------
    
     /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    // 其实这里是有返回值的
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    -----------add部分 end--------------------

调用add()实际就是调用HashMap的put方法,注意此时put的val是固定式<font color="red">**PRESENT**</font>是final修饰的一个对象,针对所有key都一样,因为我们是set所有val对我们来说没啥用。但是我们注意到其

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

并不关注这个val。

  public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

-----------
    
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 重点在这里
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        
        // 当我们第二次加入type=1时代码肯定就走到了这里,return oldValue其实也就是returun我们传入的PRESENT not nul
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

​ 重点在这里

​ 重点在这里

​ 重点在这里

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

​ 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属性,这些都不需要考虑了。

转载于:https://my.oschina.net/fshuqing/blog/3035441

分享到:
评论

相关推荐

    Java中List集合对象去重及按属性去重的8种方法

    以下文章将详细介绍8种针对List集合对象去重的方法,包括基于对象整体以及按特定属性进行去重。 ### 一、对象整体去重 #### 1. 使用Set(HashSet) 这是最基础的方法,通过将List转换为Set,由于Set不允许重复...

    php数组去重和js数组去重最简方法

    总结来说,PHP和JavaScript中数组去重的方法各有特色,PHP需要针对二维数组自定义解决方案,而JavaScript则提供了原生的Set对象和现代语法糖。理解这些方法并根据具体场景灵活运用,能提高代码质量和效率。

    JS实现数组简单去重及数组根据对象中的元素去重操作示例

    本篇文章将探讨两种在JavaScript中实现数组去重的方法:一种是针对基础类型的数组,另一种是针对包含对象的数组。 ### 一、基础类型数组去重 基础类型的数组去重通常涉及到简单的逻辑判断。以下是一个使用哈希表...

    原生JS去重-两种方法去掉重复字符

    在JavaScript编程中,数据处理是常见的任务之一,尤其是在处理字符串或数组时,有时我们需要去除其中的重复元素。...在实际应用中,还可以结合其他数据结构和方法,如Map、reduce等,来解决更复杂的数据去重问题。

    js代码-015手写代码练习----去重

    这些方法各有优缺点,如Set和Map对象在处理复杂数据类型时更为方便,但不是所有浏览器都支持;filter和reduce在处理大量数据时可能性能较低。选择哪种方法取决于具体需求和目标环境。 在压缩包中的`main.js`文件很...

    JS算法 数据结构 精华集.zip

    本“JS算法 数据结构 精华集”正是针对这一主题,通过深入学习,你可以提升JS编程能力,并在实际工作中更高效地解决问题。 首先,让我们来看看数据结构。数据结构是组织和管理数据的方式,常见的有数组、链表、栈、...

    NOIP2007.rar_STL

    《NOIP2007.rar_STL》是一个针对全国青少年信息学奥林匹克竞赛(NOIP)2007年的备考资源,特别关注C++编程语言中的STL(Standard Template Library,标准模板库)以及在竞赛中常用的算法。STL是C++编程中一个极其...

    大数据必修课 Python基础入门教程 Python自学资料课件-第4章 Python组合数据类型 共60页.pptx

    本课程是针对大数据应用人才设计的一门Python基础入门教程,由清华大学出品,适用于初学者和有一定经验的开发者进行学习和复习。课程内容包括Python3的基础概念、语法、流程控制、数据类型、正则表达式、函数、模块...

    Python面试大全(详细)

    9. **函数和装饰器**:函数参数传递通常是按值传递,但复杂对象(如列表、字典)传递的是引用。装饰器允许在不修改原有函数代码的情况下增加功能,如`@staticmethod`、`@classmethod`和`@property`。 10. **面向...

    LeetCode:包含针对不同leetcode问题的解决方案

    - **集合(Set)**: 无序且不重复的元素集合,可用于去重或进行集合运算。 - **堆(HeapQ)**: Python的heapq模块提供了堆数据结构,适用于优先队列等场景。 4. **算法** - **排序算法**: 快速排序、归并排序、插入...

    用友U8_EAI数据接口常见问题.doc

    - **问题分析**:此类错误通常是因为权限不足或对象引用未正确设置导致的。 - **解决方案**: - 检查登录凭据是否正确。 - 确认用户账号具有足够的权限访问相关资源。 - 根据错误提示调整代码逻辑,确保对象引用...

    ACM/ICPC 代码库

    4. 算法库:如std::sort、std::find、std::unique等,可以高效地处理各种排序、查找和去重问题。 5. 栈和队列:std::stack和std::queue,常用于模拟算法中的栈和队列操作。 6. 优先队列(Priority Queue,pq):...

    大前端面试小册.pdf

    - **去重方法**:对比不同的数组去重技术,包括基于对象和基于`Set`的方法。 #### 异步编程 - **单线程模型**:解释JavaScript为何采用单线程模型。 - **异步处理**:使用回调函数、事件循环等技术解决异步问题。...

    J2EE高性能编程

    综上所述,通过合理选择数据结构、优化内存管理、改进循环逻辑以及针对特定语言(如SQL和JavaScript)的优化技巧,可以在很大程度上提高J2EE应用的性能。这些实践不仅能够提升程序运行速度,还能增强系统的可维护性...

    面向生信专业的数据结构与算法,选用Python语言,以理解原理、掌握应用为主

    此外,贪心算法、动态规划和回溯法也是解决复杂生物问题的利器。例如,动态规划在解决最优剪接问题、蛋白质结构预测等方面有广泛应用。Python的递归功能使得实现这些高级算法变得简单易懂。 在学习过程中,建议通过...

    28个java常用的工具类.rar

    2. **集合操作**:Java集合框架包括`List`、`Set`、`Map`等接口,但有时我们需要对集合进行更复杂的操作,如排序、去重、合并等。工具类可能提供这些便捷的集合处理方法。 3. **日期与时间**:处理日期和时间在Java...

    Python进阶.pdf

    - 用于去重、成员关系测试和集合运算,提高数据处理效率。 6. **装饰器(Decorators)**: - 装饰器是Python的一种函数,用于修改或增强其他函数的功能,如添加日志、性能监控等。 - `@decorator`语法糖使得装饰器...

    阿里巴巴Java开发手册2018-详尽版

    2. List与Set:根据业务需求选择合适的数据结构,List适合顺序访问,Set适合去重。 四、并发编程 1. 线程安全:尽量避免使用wait/notify,推荐使用并发工具类如Semaphore、CountDownLatch等。 2. 线程池:合理设置...

    Redis高并发问题的解决方法

    - 根据业务需求选择合适的数据结构,例如,使用Set代替List处理去重,使用SortedSet进行范围查询,或者使用Hashes进行复杂对象的存储,都能优化性能。 7. **预加载与缓存策略**: - 对于热点数据,可以考虑预先...

    python-日常学习随笔

    15. **面向对象编程**:Python支持面向对象编程,通过类和对象,我们可以构建复杂的数据结构和行为。 学习Python的数据处理,不仅需要理解以上基础知识,还需要不断地实践和探索。"downey"这个文件名可能指的是...

Global site tag (gtag.js) - Google Analytics