Java Stream distinct
最后修改时间 2025 年 5 月 8 日
本文演示了如何使用 Java Stream distinct 方法从流中删除重复元素。
distinct 方法是 Java Streams 中的一个中间操作,它过滤掉重复元素,确保流中只剩下唯一值。它基于元素的 equals
方法确定唯一性。
对于有序流,distinct
保持原始的遇到顺序,保留元素出现的顺序。 相反,对于无序流,删除重复项可以通过减少不必要的顺序跟踪开销来提高性能。
基本 distinct 语法
distinct
方法提供了一种简单的方法来从流中删除重复元素,确保只保留唯一值。
Stream<T> distinct()
此操作依赖于 equals
方法来比较元素并识别重复项。 为了确保正确的行为,流元素应该适当地实现 equals
和 hashCode
方法。 不正确的实现可能会在过滤唯一元素时导致意外的结果。
distinct 的内部工作原理
Java Streams 中的 distinct
方法不是基于哈希或键值存储(如 HashMap
)。 而是执行有状态过滤,以确保流中仅保留由其 equals
方法确定的唯一元素。
功能 | Stream | HashMap |
---|---|---|
目的 | 动态处理和转换数据 | 高效存储键值对 |
数据存储 | 不存储元素 | 在哈希结构中存储元素 |
唯一性逻辑 | 在 distinct 中使用 equals | 使用哈希进行快速查找 |
性能 | 对于大型数据集可能较慢 | 针对 O(1) 键查找进行了优化 |
在内部,distinct
通过使用 LinkedHashSet
跟踪先前看到的元素来维护有状态的过滤器。 当处理流时,针对已经看到的元素检查每个元素的相等性。 如果元素是唯一的(根据 equals
),则将其传递到下游;否则,将其过滤掉。 这种方法保留了遇到顺序,但对于非常大的数据集来说,效率可能不如哈希。
与使用哈希提供快速 O(1) 查找的 HashMap
不同,流中的 distinct
不会索引元素以进行快速访问。 而是顺序比较每个元素,这会影响大型流的性能。
从原始值中删除重复项
distinct
方法可以与原始值流一起使用,以消除重复项并仅保留唯一元素。
void main() { Stream.of(2, 5, 3, 2, 5, 7, 3, 8) .distinct() .forEach(System.out::println); }
此示例从流中删除重复的整数。 distinct
操作保留每个唯一数字的首次出现。
$ java Main.java 2 5 3 7 8
删除重复的字符串
distinct
方法也可以应用于字符串流,以过滤掉重复的值并仅保留唯一的字符串。
void main() { Stream.of("apple", "orange", "apple", "banana", "orange") .distinct() .forEach(System.out::println); }
此示例从流中删除重复的字符串。 字符串比较区分大小写,因此 "Apple" 和 "apple" 将被视为不同的。
$ java Main.java apple orange banana
具有 equals/hashCode 的自定义对象
当将 distinct
与自定义对象一起使用时,重要的是对象正确实现 equals
和 hashCode
以确保正确识别重复项。
record Person(String name, int age) { } void main() { Stream.of( new Person("Alice", 30), new Person("Bob", 25), new Person("Alice", 30), new Person("Charlie", 35), new Person("Bob", 25) ) .distinct() .forEach(p -> System.out.println(p.name() + " - " + p.age())); }
此示例删除重复的 Person 对象。 记录会自动实现正确的hashCode
方法,基于它们的组件。
$ java Main.java Alice - 30 Bob - 25 Charlie - 35
没有正确 equals/hashCode 的自定义对象
如果自定义对象未正确实现 equals
和 hashCode
,则 distinct
方法可能无法按预期识别重复项。
class Product { String name; double price; Product(String name, double price) { this.name = name; this.price = price; } // No equals/hashCode implementation } void main() { Stream.of( new Product("Laptop", 999.99), new Product("Phone", 699.99), new Product("Laptop", 999.99) ) .distinct() .forEach(p -> System.out.println(p.name + " - " + p.price)); }
此示例表明,如果没有正确的 equals
和 hashCode
方法,distinct
将无法按预期工作,会将具有相同值的对象视为不同的对象。
$ java Main.java Laptop - 999.99 Phone - 699.99 Laptop - 999.99
与其他操作结合使用
distinct
方法可以与其他流操作(例如过滤和映射)结合使用,以创建更复杂的数据处理管道。
void main() { Stream.of("apple", "banana", "apple", "orange", "banana", "kiwi") .filter(s -> s.length() > 4) .distinct() .map(String::toUpperCase) .forEach(System.out::println); }
此示例过滤长水果,删除重复项,并转换为大写,展示了 distinct 如何与其他操作组合使用。
$ java Main.java BANANA ORANGE
Distinct 与嵌套集合
在将嵌套集合展平为单个流后,distinct
方法可用于删除重复项。
void main() { List<List<String>> nestedLists = List.of( List.of("a", "b", "c"), List.of("b", "c", "d"), List.of("c", "d", "e") ); nestedLists.stream() .flatMap(List::stream) .distinct() .forEach(System.out::println); }
此示例展平嵌套列表,然后删除重复元素,演示了 distinct 的常见用例。
$ java Main.java a b c d e
文本文件中的不同单词
distinct
方法可用于从文本文件中提取所有唯一的单词,忽略大小写和标点符号,这对于构建词汇表列表或分析文档中的唯一单词等任务很有用。
The Battle of Thermopylae was fought between an alliance of Greek city-states, led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the course of three days, during the second Persian invasion of Greece.
此文件包含对温泉关战役的简要描述。 我们可以使用 distinct
方法从该文本文件中提取所有唯一的单词,忽略大小写和标点符号。
void main() throws IOException { Path path = Paths.get("thermopylae.txt"); Files.lines(path) .flatMap(line -> Arrays.stream(line.split("\\W+"))) .map(String::toLowerCase) .filter(s -> !s.isEmpty()) .distinct() .forEach(System.out::println); }
此示例从文件中读取行,将它们拆分为单词,将它们规范化为小写,删除空字符串,并打印所有唯一的单词。 split("\\W+")
正则表达式根据任何非单词字符进行拆分,从而有效地删除标点符号。
来源
在本文中,我们探讨了 Java Stream distinct 方法。 它提供了一种有效的方法来从流中删除重复元素,但需要正确实现自定义对象的 equals
和代码。 了解 distinct 对于处理可能包含重复项的数据至关重要。
作者
列出所有Java教程。