Java Set集合去重机制

本文深入解析Java集合Set的去重原理,并通过实例展示了如何自定义复杂对象的equals和hashCode方法以实现Set集合中复杂对象的去重功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


关于java里面的集合,大家经常利用到Set集合没有重复数据的特性,来进行数据的去重,那么去重的原理是怎么样的呢?


最近面试了几个人,其间有聊到集合的东西,所以就顺便问了一下这个问题,但是都是只知道这么用,

而没有去看看底层代码的去重原理(而恰恰有可能这些基础原理会被用来设计其他一些场景实现),

所以在此文章记录一下,希望能帮助到一些人:


下面是Set集合的类图:




下面我们来跟踪一下执行的过程:

1. 首先我们实例化一个set对象

Set<8大基本类型> set = new HashSet<8大基本类型>();
set.add(8大基本类型);
2.add操作会调用HashSet中的add方法,实现如下:

public boolean add(E e) {
	return map.put(e, PRESENT)==null;
    }
3.HashSet中的add方法依赖了HashMap的put方法,实现如下:

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//每添加一个,则循环判断是否与map中的元素相等
            Object k;
            // 先判断hashcode是否一致,然后再判断值是否相等
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

很明显上述操作对8种基本类型的数据+String型是有用的,但是如果想把去重的方式应用到复杂的对象呢,上述方式就还欠缺了一点了,

知道了执行顺序和原理了的话,就知道该如何去实现了!

下面就是重写对象User的实现,重写equals和hashCode方法!

测试类:

package com.test.set;

import java.util.HashSet;
import java.util.Set;

public class UniqueSet {

    /**
     * 
     * @param args
     */
    public static void main(String[] args) {

        User user1 = new User(1, "a");
        User user2 = new User(2, "b");
        User user3 = new User(3, "c");
        User user4 = new User(2, "b");

        Set<User> userSet = new HashSet<User>();
        userSet.add(user1);
        userSet.add(user2);
        userSet.add(user3);
        userSet.add(user4);
        // 输入结果 id=1 username=a  id=2 username=b  id=3 username=c  
        for (User u : userSet) {
            System.out.println("id=" + u.id + " " + "username=" + u.username);
        }
    }
}

实现类:

package com.test.set;

/**
 * 类描述:set集合针对String 类型和8大基础数据类型  过滤掉重复数据,
 * 如果存放的是其他类型对象,则需要重写hashCode方法和equals方法,
 * 当hashcode相等时(先执行hashCode方法),则会去执行equals方法,比较每个属性的值
 * 如果一致的话,则不会存进set,否则加入set集合 
 * 
 */
public class User {

    // id
    protected Integer id;
    // 名称
    protected String  username;

    //构造方法
    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }

    /** 
     * 如果对象类型是User,先比较hashcode,一致的场合再比较每个属性的值
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (this == obj)
            return true;
        if (obj instanceof User) {
            User user = (User) obj;
            // if(user.id = this.id) return true; // 只比较id  
            // 比较每个属性的值 一致时才返回true 
            if (user.id == this.id && user.username.equals(this.username))
                return true;
        }
        return false;
    }

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



以上---------------------





### 方法概述 对于Java中的集合操作,存在多种方式来实现这一目标。当面对简单的对象列表并希望除其中的复项时,可以采用`HashSet`来进行处理[^1]。 ```java // 使用HashSet进行简单 List<String> listWithDuplicates = Arrays.asList("apple", "banana", "orange", "apple"); Set<String> setWithoutDuplicates = new HashSet<>(listWithDuplicates); ``` 如果需要基于对象的特定字段执行更复杂的逻辑,则可以通过Stream API结合自定义比较器完成此任务[^2]。 ```java import java.util.*; import java.util.stream.Collectors; class Employee { private String id; private String name; public Employee(String id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o){ if (this == o) return true; if (!(o instanceof Employee)) return false; Employee employee = (Employee) o; return Objects.equals(id, employee.id); } @Override public int hashCode(){ return Objects.hash(id); } } public class Main { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee("001","Alice"), new Employee("002","Bob"), new Employee("001","Charlie") // Duplicate ID with different names. ); Map<String, Employee> uniqueEmployeesById = employees.stream() .collect(Collectors.toMap(Employee::getId, e -> e,(existing, replacement) -> existing)); List<Employee> result = new ArrayList<>(uniqueEmployeesById.values()); System.out.println(result.toString()); } } ``` 另外一种情况是在不改变原有数据结构的前提下获取唯一元素组成的列表,此时可借助于LinkedHashMap保留插入顺序的同时达到的效果[^4]。 ```java // 利用LinkedHashMap保持原始顺序并移除复条目 List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 5, 3); LinkedHashMap<Integer, Void> map = new LinkedHashMap<>(); for(Integer num : numbers){ map.put(num,null); } numbers.clear(); numbers.addAll(map.keySet()); System.out.println(numbers); // 输出:[1, 2, 3, 4, 5] ``` 以上展示了三种不同场景下的Java集合方案,每种都有其适用范围和特点。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值