如何通过自定义比较(不实现equals)高效移除List中第三方类重复实例?
基于Comparable接口的Stream高效去重方案(针对未实现equals的第三方类)
问题背景
使用第三方库的List<SomeLibraryClass>集合需要去重,该类未实现equals方法,但提供了符合Comparable接口的compareTo方法(返回-1、0、1),这是判断实例是否重复的唯一依据,无法通过字符串操作替代。不想重写该类的equals方法,因此new HashSet<>(myList)这类依赖equals的方案无效。尝试过Collection#removeIf但效率偏低,Collectors#toMap的合并函数也未成功,希望用Stream API实现高效去重。
示例代码
主方法:
public static void main(String[] args) { List<SomeLibraryClass> myObjs = List.of( new SomeLibraryClass(), // 生成 "abc" new SomeLibraryClass(), // 生成 "bca" new SomeLibraryClass(), // 生成 "def" new SomeLibraryClass(), // 生成 "def" new SomeLibraryClass(), // 生成 "fed" new SomeLibraryClass() // 生成 "ghi" ); // 按字符串字符顺序无关的规则去重(依赖compareTo逻辑) assert(myObjs.size() == 3); // 最终等价于保留abc、def、ghi三类实例 }
第三方类结构:
class SomeLibraryClass { private String someProperty; SomeLibraryClass() { // 内部逻辑生成someProperty } public String getSomeProperty() { return someProperty; } // 未实现equals方法 // 已实现Comparable<SomeLibraryClass>接口的compareTo方法,用于判断实例是否重复 }
高效去重方案
方案1:利用TreeSet自动去重(简洁但改变顺序)
TreeSet会通过Comparable的compareTo方法判断元素是否相等(返回0则视为重复),结合Stream收集器实现去重:
List<SomeLibraryClass> distinctList = myObjs.stream() .collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet<>(SomeLibraryClass::compareTo)), ArrayList::new ));
- 注意:如果
SomeLibraryClass本身已实现Comparable接口,TreeSet可直接使用无参构造new TreeSet<>()。 - 特点:代码简洁,时间复杂度O(n log n),但会改变原列表的元素顺序(按
compareTo排序)。
方案2:Filter结合TreeSet保留原顺序(高效推荐)
通过维护一个TreeSet记录已出现的元素,利用filter过滤重复项,同时保留原列表中元素的首次出现顺序:
TreeSet<SomeLibraryClass> seen = new TreeSet<>(SomeLibraryClass::compareTo); List<SomeLibraryClass> distinctList = myObjs.stream() .filter(seen::add) .collect(Collectors.toList());
- 原理:
TreeSet.add()方法会返回false当元素已存在(通过compareTo判断),filter会保留返回true的元素,即首次出现的元素。 - 特点:时间复杂度O(n log n),效率远高于O(n²)的
removeIf,同时保留原顺序,是最优方案。
方案3:自定义收集器(灵活控制)
如果需要更自定义的逻辑,可编写自定义收集器:
List<SomeLibraryClass> distinctList = myObjs.stream() .collect( ArrayList::new, (list, elem) -> { // 检查列表中是否已有等价元素 boolean exists = list.stream().anyMatch(e -> e.compareTo(elem) == 0); if (!exists) { list.add(elem); } }, (list1, list2) -> { // 合并两个列表时去重 list2.forEach(elem -> { if (!list1.stream().anyMatch(e -> e.compareTo(elem) == 0)) { list1.add(elem); } }); } );
- 特点:逻辑灵活,但时间复杂度O(n²),数据量大时效率偏低,仅适合小集合场景。
内容的提问来源于stack exchange,提问作者siggi
相关产品推荐
相关产品推荐

