章
目
录
在Java 8中添加的Stream.distinct()方法返回由给定Stream中唯一元素组成的新Stream。distinct()操作从流中删除重复的元素,确保只保留结果流中的唯一元素。
List<T> distinctItems = stream.distinct().toList();
1. Stream.distinct() 方法
distinct()是一种有状态的中间操作,它在处理新项目时使用之前从流中看到的元素的状态。
Stream<T> distinct()
- distinct()返回给定流中的唯一元素。用于检查流元素的相等性,使用equals()方法。
- 对于由有序集合支持的流,distinct()保证顺序。
- 对于无序的流,没有稳定性保证。
2. 在字符串或原始类型的Stream中找到不同的元素
从String和包装类等简单类型的列表中找到不同的项很容易。这些类实现了必需的equals()方法,该方法比较存储在其中的值。
在给定示例中,我们有一个字符串列表,我们想从中找到所有不同的字符串。我们将使用Stream来迭代所有String元素,并使用Stream.collect()终端操作将不同的String元素收集到另一个List中。
Collection<String> list = Arrays.asList("A", "B", "C", "D", "A", "B", "C");
List<String> distinctChars = list.stream()
.distinct()
.collect(Collectors.toList()); //[A, B, C, D]
3.按照字段或属性进行Stream去重
在现实应用中,我们将处理自定义类或复杂类型的Stream(表示系统实体)。
默认情况下,所有Java对象都继承自Object类的equals()方法。默认的equals()方法比较引用以检查两个实例的相等性。因此,强烈建议重写equals()方法并定义自定义逻辑以确定对象的相等性。如果在自定义类型中没有重写equals()方法,那么在从Stream中找到不同的元素时可能会出现奇怪的行为。
3.1重写equals()以定义对象相等性
让我们创建一个Person类作为示例。它有三个字段:id、fname和lname。如果两个人的id相同,则它们是相等的。不要忘记重写equals()方法,否则对象的相等性将无法按预期工作。
public record Person(Integer id, String fname, String lname) {
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Person other = (Person) obj;
return Objects.equals(id, other.id);
}
}
3.2示例
让我们测试代码。我们将向List中添加几个重复的人员记录。然后,我们将使用Stream.distinct()方法找到所有具有唯一id的Person类实例。
Person lokeshOne = new Person(1, "Lokesh", "Gupta");
Person lokeshTwo = new Person(1, "Lokesh", "Gupta");
Person lokeshThree = new Person(1, "Lokesh", "Gupta");
Person brianOne = new Person(2, "Brian", "Clooney");
Person brianTwo = new Person(2, "Brian", "Clooney");
Person alex = new Person(3, "Alex", "Kolen");
Collection<Person> list = Arrays.asList(alex,
brianOne,
brianTwo,
lokeshOne,
lokeshTwo,
lokeshThree);
// 根据id去重
List<Person> distinctElements = list.stream()
.distinct()
.collect( Collectors.toList() );
System.out.println( distinctElements );
程序输出:
[
Person [id=1, fname=Lokesh, lname=Gupta],
Person [id=2, fname=Brian, lname=Clooney],
Person [id=3, fname=Alex, lname=Kolen]
]
4.根据复杂键或多个字段查找不同的项目
我们可能并不总是根据自然等式规则获得不同的项。有时,业务可能需要根据自定义逻辑找到不同的项。例如,我们可能需要找到所有具有相同id但完整名称相同的人员。在这种情况下,我们必须根据Person类的fname和lname字段检查相等性。
Java没有任何内置API可用于查找使用提供的用户函数进行比较时不同的对象。因此,我们将创建自己的实用程序函数并使用它。
我们可以使用链接的帖子上的信息来查找由多个字段组成的不同的项。
4.1. 创建distinctByKey()方法
distinctByKey()方法使用一个ConcurrentHashMap实例来检查是否存在具有相同值的现有键,其中键是从函数引用来获取的。
该函数的参数是一个lambda表达式,用于生成用于比较的映射键。如果使用的键是自定义类型,请不要忘记重写hashCode()和equals()方法。
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;
}
我们可以将任何字段-getter方法作为参数传递,这将导致字段值作为映射的键。
4.2示例
查看我们在filter()方法中使用distinctByKey(p -> p.getFname() + ” ” + p.getLname())的方式。
Person lokeshOne = new Person(1, "Lokesh", "Gupta");
Person lokeshTwo = new Person(2, "Lokesh", "Gupta");
Person lokeshThree = new Person(3, "Lokesh", "Gupta");
Person brianOne = new Person(4, "Brian", "Clooney");
Person brianTwo = new Person(5, "Brian", "Clooney");
Person alex = new Person(6, "Alex", "Kolen");
Collection<Person> list = Arrays.asList(alex,
brianOne,
brianTwo,
lokeshOne,
lokeshTwo,
lokeshThree);
// 根据key去重
List<Person> distinctElements = list.stream()
.filter( distinctByKey(p -> p.getFname() + " " + p.getLname()) )
.collect( Collectors.toList() );
System.out.println( distinctElements );
程序输出:
[
Person [id=1, fname=Lokesh, lname=Gupta],
Person [id=4, fname=Brian, lname=Clooney],
Person [id=6, fname=Alex, lname=Kolen]
]
5.结论
Stream.distinct()的主要目的是从给定流中消除重复元素,确保只有不同的元素保留在结果流中。当应用于流时,distinct()操作利用流中对象的equals()和hashCode()方法来识别和删除重复项。
在过滤掉重复项时,distinct()操作保留流中元素的原始顺序。