今天遇到一个需求,需要对一个新闻列表根据标题进行去重,总结一下方法。
1,stream().distinct()
这个方法是Java8的Stream接口直接提供的方法,看起来最简单易用。
但是 distinct() 具体是使用对象的 hashCode() 和equals() 方法来判断列表中元素是否为同一个元素,所以必须重写对象的 hashCode 和 equals 方法,这就很麻烦了,而且之后如果元素如果有属性变动,可能还得修改,给自己埋坑。
Book对象重写hashCode() 和equals()
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
final Book book = (Book) obj;
if (this == book) {
return true;
} else {
return (this.name.equals(book.name) && this.price == book.price);
}
}
@Override
public int hashCode() {
int hashno = 7;
hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
return hashno;
}
实际调用
list.stream().distinct().forEach(b -> System.out.println(b.getName()+ "," + b.getPrice()));
特别说明:
distinct() 执行有状态的中间操作,在有序流的并行流的情况下,保持 distinct() 的稳定性是需要很高的代价的,因为它需要大量的缓冲开销。如果我们不需要保持遭遇顺序的一致性,那么我们应该可以使用通过 Stream.unordered() 使用无序流来提升性能。
lomok插件的影响:
项目中我发现自己没有重写Bean对象的 hashCode 和 equals 方法,使用 distinct 也可以得到正确的去重结果。仔细思索,发现是 lomok 插件的影响,我使用 @data 或 @EqualsAndHashCode 注解会自动为对象重写 hashCode equals 方法,可以去项目的/target/classes文件夹下找到该类。
2,自定义去重函数(推荐)
这也是网上的方法,自己写个工具函数进行去重,调用Stream的filter方法过滤数据。
简单好用,而且可定制性很强,key函数根据实际需要随便改动,不影响其他地方。
底层使用了 ConcurrentHashMap 进行边查边改,简单易懂。
去重工具函数:
static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object,Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
调用过程:
list.stream().filter(distinctByKey(item -> item.getName()));