Java8 新特性之 Stream 实现集合去重

1,076 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

1. 集合中元素去重操作

去重操作是程序编写过程中常用的操作,Java 的 list 集合是允许重复元素存在的,对于简单的基本类型等,我们可以使用 Set 等方式实现去重,但是当容器元素为对象时,并不能按照属性值来判断重复。

Stream 中也提供了方法帮助我们快速的实现 List 集合元素去重。

2. String 类型集合实现去重

String 类重写了 equals() 方法,因此可以直接使用 distinct() 方法实现去重

List<String> newList = list.stream().distinct().collect(Collectors.toList());

3. 对象去重

Java 中的每个对象地址都是不同的,如果需要使用对象内容(值)比较去重,需要重写对象的 equals() 方法和 hashCode() 方法。

// contains() 方法去重,根据 equals() 来比较的,需要重写 equals()
List<User> newList = new ArrayList<>();
list.stream().forEach(
    a -> {
        if (!newList.contains(a)) {
            newList.add(a);
        }
    }
);

4. 根据对象的某个属性值去重

除了针对对象本身去重外,根据对象中某个属性去重也比较常见,此时就需要针对属性定义特定的判定规则,如通过 Comparator 比较器设置比较两个对象的属性值,达到过滤的目的。

4.1 Set 去重

使用 Set 去重,创建 Set 时定义 Set 判重机制为元素对象的属性,然后利用 Set 容器的不重复机制,实现 list 集合中元素去重。

// Set 容器去重
Set<User> userSet = new TreeSet<>((o1, o2) -> o1.getName().compareTo(o2.getName()));
userSet.addAll(list);
List<User> newList = new ArrayList<>(userSet);

4.2 Stream 配合 TreeSet 去重

Stream 中提供的 collectingAndThen() 也可以配合 TreeSet 实现自定义去重,但是使用TreeSet 实现集合去重后会打乱原有顺序,按照 TreeSet 的顺序重写排列。

// Stream
List<User> newList = list.stream().collect(
    collectingAndThen(
        toCollection(() -> new TreeSet<>(comparingLong(Person::getId))), ArrayList::new
    )
);

4.3 filter() 实现去重

自定义 filter() 方法的 Predicate 参数方法来实现 filter() 去重。

// filter 过滤去重
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
    Map<Object, Boolean> map = new ConcurrentHashMap<>();
    return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

// 调用断言方法实现去重
List<User> newList = list.stream().filter(distinctByKey(p -> p.getId()));

5. 根据对象属性去重后保留属性集合

如果只需要不重复的属性,可以使用 map() 方法先获取集合中元素对应属性的映射集合,之后就可以转换成 String 类型集合的去重实现。

// 使用 map 仅保留不重复的属性集合
list.stream().map(User::getName).distinct().collect(Collectors.toList());