集合去重的常用技巧——自定义类去重

上文:《面试被问到 HashMap 有这一文就够了!》

Set 中的元素保证唯一性,因此可以借助 Set 集合的这个特性对其他集合中的元素进行去重操作:

public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,2,3,4,5,5,6));
        Set<Integer> set = new HashSet<Integer>(list);
        System.out.println(set);
} //[1, 2, 3, 4, 5, 6]

这样可以轻松地对 int(Integer)、double(Double)、String。。。等等基本类型及其包装类的集合进行去重,而不必去显示的编写一个 for 循环。

在日常编码中经常遇到自定义类集合去重的需求:

@AllArgsConstructor
public class BasicInfo {
    private Long basicInfoId;
    private String empId;
    private String name;
    private Integer gender;
    private String company;
     @Override
    public String toString() {。。。}
    。。。
}

假如因为多表连接时因为某些表中数据的特性,不可避免的会导致 list 中存在相同的元素:

//根据上述类的定义,不难看出数据库中表的部分定义
//loadBasicInfo()方法会查出 BasicInfo 类实例的集合
List<BasicInfo> list = paMapper.loadBasicInfo(xxx,xxx,xxx,xxx);

PS:假设每条数据的 basicInfoId 和 empId 都可以唯一的标识一条数据。(为了方便举例,应用时结合实际数据特点)

TreeSet 去重

这时最"笨"的方法就是用 for+contains 的方法去遍历去重;其实我们依然可以使用 Set 集合的特性去对这个自定义类集合去重。原理如下(在前文的末尾有详细介绍):

  • Comparable 接口出自 java.lang 包下,接口下只有一个 compareTo(T o)抽象方法,如果希望 TreeMap/TreeSet 插入元素时希望采用自定义排序,或者希望自定义 Set 去重规则,可以让插入对象实现这个接口重写方法:

    public int compareTo(T o);
    
  • Comparator 是个函数式接口出自 java.util 包下,这个接口下方法有二十多个方法,其中有一个抽象方法 compare(T o1, T o2),其常用方式是调用带参数的Collections.sort()时传一个 Comparator 的匿名类,支持采用 Lambda 的方式实现:

    int compare(T o1, T o2);
    

当我们需要对一个集合进行自定义排序或者自定义 Set 去重规则时,可以重写 compare(T o1, T o2)或者 compareTo(T o)

例如:当我们需要对某一个集合实现两种自定义排序的时候,比如对 Student 对象元素中的姓名采用一种自定义排序方式、学校名采用另一种自定义排序方式的需求,可以重写 compareTo(T o)方法实现一种、再实现 Comparable 接口实现另一种方法,也可以用两个 Comparator 分别重写compareTo(T o)方法进行实现,第二个方案可以使用两个带参数的 Collections.sort()实现。

直接看例子:

public static void main(String[] args) {
        //模拟数据
        BasicInfo info1 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
        BasicInfo info2 = new BasicInfo(112L, "Key002", "李四", 0, "百毒");
        BasicInfo info3 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
        BasicInfo info4 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
        BasicInfo info5 = new BasicInfo(113L, "Key003", "王五", 1, "疼讯");
        BasicInfo info6 = new BasicInfo(112L, "Key002", "李四", 0, "百毒");
        List<BasicInfo> list = new ArrayList<BasicInfo>(Arrays.asList(info1,info2,info3,info4,info5,info6));
        //去重
        Comparator<BasicInfo> comparator = new Comparator<BasicInfo>() {
            public int compare(BasicInfo e1, BasicInfo e2) {
                return e1.getBasicInfoId().compareTo(e2.getBasicInfoId());
            }
        };
        Set<BasicInfo> set = new TreeSet<BasicInfo>(comparator);
        set.addAll(list);
        for (BasicInfo info : set) {
            System.out.println(info);
        }
    }
//输出:
//BasicInfo{basicInfoId=111, empId='Key001', name='张三', gender=1, company='阿狸'}
//BasicInfo{basicInfoId=112, empId='Key002', name='李四', gender=0, company='百毒'}
//BasicInfo{basicInfoId=113, empId='Key003', name='王五', gender=1, company='疼讯'}

HashSet去重

HashSet 的数据结构是直接 new 了一个 HashMap,由于 HashMap 的 Key 是唯一的,HashSet 正是利用这一性质进行去重。(转化为自定义类做 HashMap 的 Key 的问题)

HashSet 在元素插入时,先调用 HashMap 的 hash()方法计算插入对象的 hashcode值去得到对象加入的位置,同时会和集合中其他的对象的 hashcode 值进行比较,如果没有相同的 hashcode 值则说明对象没重复;如果 hashcode 值重复,这是会调用对象的equals()方法来检查 hashcode 相同的对象是否真的相同,最终相同则插入失败,否则成功。

前文有详细介绍:《面试被问到 HashMap 有这一文就够了!》

所以利用 HashSet 去重(当自定义类作为 HashMap 的 Key 的时候)需要去重写自定义类的 hashCode() 方法和 equals() 方法。我们经常用 String 作为 HashMap 的 Key 并且没有重写其方法,是因为 String 在设计是已经重写了 hashCode() 和 equals() 方法 。

去重示例:

@AllArgsConstructor
public class BasicInfo {
    private Long basicInfoId;
    private String empId;
    private String name;
    private Integer gender;
    private String company;

    @Override
    public String toString() {
        return "BasicInfo{" +
                "basicInfoId=" + basicInfoId +
                ", empId='" + empId + '\'' +
                ", name='" + name + '\'' +
                ", gender=" + gender +
                ", company='" + company + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof BasicInfo) {
            //根据真实数据情况而定
            return ((BasicInfo) o).basicInfoId == this.basicInfoId;
        }
        return false;
    }

    @Override
    public int hashCode() {
        //根据真实数据情况而定
        return this.basicInfoId.intValue();
    }
}
public static void main(String[] args) {
    //模拟数据
    BasicInfo info1 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
    BasicInfo info2 = new BasicInfo(112L, "Key002", "李四", 0, "百毒");
    BasicInfo info3 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
    BasicInfo info4 = new BasicInfo(111L, "Key001", "张三", 1, "阿狸");
    BasicInfo info5 = new BasicInfo(113L, "Key003", "王五", 1, "疼讯");
    BasicInfo info6 = new BasicInfo(112L, "Key002", "李四", 0, "百毒");
    List<BasicInfo> list = new ArrayList<BasicInfo>(Arrays.asList(info1,info2,info3,info4,info5,info6));
    //去重
    Set<BasicInfo> set = new HashSet<BasicInfo>(list);
    for (BasicInfo info : set) {
        System.out.println(info);
    }
}
//输出:
//BasicInfo{basicInfoId=112, empId='Key002', name='李四', gender=0, company='百毒'}
//BasicInfo{basicInfoId=113, empId='Key003', name='王五', gender=1, company='疼讯'}
//BasicInfo{basicInfoId=111, empId='Key001', name='张三', gender=1, company='阿狸'}

注意:用 Lombok 的 @Data 注解会自动生成 get、set、equals、canEqual、hashCode、toString

如果希望去重后仍然保持原集合中元素的顺序,可以使用 LInkedHashMap

虽然不难,但是这是日常编码中比较常用到的点,面试时也会偶尔被提起,还是很有必要去学习并应用的。


菜鸟本菜,不吝赐教,感激不尽!

更多题解源码和学习笔记:githubCSDNM1ng

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值