Java集合之List去重

2,478 阅读1分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

List 去重的方式较多,本人对常用的几种去重方式进行整理、分析,如有错漏欢迎指正。

HashSet

利用 Set 元素的不重复特性去重,去重后不保留原顺序。无法直接对新对象(new 创建的对象)去重。

public class Test {
    public static void main(String[] args) {
        // 1.构造 List
        List<Person> list = new ArrayList<>();
        Person p = new Person("张三", 20, '男');
        list.add(p);
        list.add(new Person("小丽", 20, '女'));
        list.add(new Person("小丽", 20, '女'));
        // 2.去重
        Set<Person> hashSet = new HashSet<>(list);
        List<Person> newList = new ArrayList<>(hashSet);
        System.out.println(newList);//没有对“小丽”去重
    }
}

HashSet + ArrayList

通过 HashSet 判断元素是否重复,不重复则放入新的 List 中。这种方法去重后保留原顺序。无法直接对新对象(new 创建的对象)去重。

Set<Person> hashSet = new HashSet<>();
List<Person> newList = new ArrayList<>();
for (Iterator<Person> iter = list.iterator(); iter.hasNext();) {
    Person element = iter.next();
    if (hashSet.add(element)) {
        newList.add(element);
    }
}

TreeSet

利用 TreeSet 的元素不重复特性去重,可自定义排序,默认自然排序

Set<String> treeSet = new TreeSet<String>(list);
List<String> newList = new ArrayList<>(treeSet);

ArrayList

使用两个 List,遍历原 List,然后通过检查新 List 中是否存在原 List 中的元素来去重,这种去重保留原顺序。无法直接对新对象(new 创建的对象)去重。

List<String> newList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
    if (!newList.contains(list.get(i))) {
        newList.add(list.get(i));
    }
}

java8 的 stream

以流的方式去重会保留原顺序。无法直接对新对象(new 创建的对象)去重。

注意:流不会对原集合进行操作,所以要用新集合接收操作后的流。

list.stream().distinct().collect(Collectors.toList());

实体单属性之自定义方法去重

上述方法不能根据实体的某个属性去重,因此只能通过自定义方法实现。利用流的 filter 来自定义方法,这种方式去重保留原顺序

public class Test {
    public static void main(String[] args) {
        // 1.构造 List
        ...
        // 2.去重
        List<Person> newList = new ArrayList<>();
        newList = list.stream()
        .filter(distinctByKey(o ->  o.getName() + ";" + o.getAge()))
        .collect(Collectors.toList());
        System.out.println(newList);
    }

    /**
     * 自定义的去重方法
     * @param <T> 待去重实体
     * @param keyExtractor 去重标记(如:o.getName() + ";" + o.getAge())
     * @return
     */
    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

}

实体单属性之 stream + TreeSet

stream + TreeSet,不保留原顺序,可自定义排序,默认自然排序

List<Person> newList = list.stream().collect(Collectors.collectingAndThen(
    Collectors.toCollection(() -> new TreeSet<>(
        Comparator.comparing(person -> person.getName() + ";" + person.getAge()))), ArrayList::new)
);

总结

stream 去重的效率最低,耗费时间大概为 HashSet + ArrayList 去重所耗时间的五倍。

建议:对性能要求不高使用 stream 方式去重,代码简洁;对性能有较高要求用 HashSet + ArrayList 或者 ArrayList ,后者性能略低,但是都比 HashSet 直接去重效率高;需要自定义排序用 TreeSet。

HashSetHashSet + ArrayListTreeSetArrayListstream
写法简单较难简单较难最简单
效率较低较低最低
顺序无序原顺序自然排序原顺序原顺序