文章目录
- [277. Java Stream API - 去重与排序:Stream 中的 `distinct()` 与 `sorted()`](#277. Java Stream API - 去重与排序:Stream 中的
distinct()与sorted()) -
-
- [🎯 目标](#🎯 目标)
- [🧼 `distinct()` ------ 去重利器](#🧼
distinct()—— 去重利器) -
- [📌 定义:](#📌 定义:)
- [🔍 `distinct()` 的内部原理](#🔍
distinct()的内部原理) - [✅ 示例:整数去重](#✅ 示例:整数去重)
- [🔽 输出:](#🔽 输出:)
- [📊 `sorted()` ------ 排序操作](#📊
sorted()—— 排序操作) -
- [📌 定义:](#📌 定义:)
- [🧠 工作机制:](#🧠 工作机制:)
- [✅ 示例 1:自然排序(String 实现了 Comparable)](#✅ 示例 1:自然排序(String 实现了 Comparable))
- [🔽 输出:](#🔽 输出:)
- [✅ 示例 2:按长度排序(使用 Comparator)](#✅ 示例 2:按长度排序(使用 Comparator))
- [🔽 输出:](#🔽 输出:)
- [🔁 无限流中的表现对比](#🔁 无限流中的表现对比)
- [✅ 总结对比](#✅ 总结对比)
- [🧠 延伸小技巧](#🧠 延伸小技巧)
-
- [🧩 想去重自定义对象?](#🧩 想去重自定义对象?)
-
277. Java Stream API - 去重与排序:Stream 中的 distinct() 与 sorted()
🎯 目标
本节将讲解如何使用 distinct() 去除重复值,使用 sorted() 对流元素排序,包括:
- 它们的工作机制
- 使用示例
- 注意事项(如可用于无限流与否)
- 性能影响
🧼 distinct() ------ 去重利器
📌 定义:
distinct() 会移除流中重复的元素,依据是元素的:
hashCode()和equals()
所以:你使用的对象必须正确实现这两个方法,才能保证去重有效。
🔍 distinct() 的内部原理
distinct() 并不像大多数中间操作那样是"无状态"的:
- 它内部维护一个
Set集合来追踪已见过的元素。 - 每处理一个元素,会尝试把它加入
Set。- ✅ 加入成功:表示是第一次出现,传递下游。
- ❌ 加入失败:说明是重复的,直接丢弃。
⚠️ 因为要维护状态,所以它会占用内存 ,但能即时输出结果。
✅ 示例:整数去重
java
List<Integer> ints = List.of(1, 4, 2, 1, 3, 3);
List<Integer> distincts = ints.stream()
.distinct()
.toList();
System.out.println("distinct ints: " + distincts);
🔽 输出:
java
distinct ints: [1, 4, 2, 3]
✅ 说明:重复的 1 和 3 被移除了,且保持原始顺序。
📊 sorted() ------ 排序操作
📌 定义:
sorted() 用于将流中的元素排序。
它有两个重载:
java
Stream<T> sorted(); // 自然顺序(元素必须实现 Comparable)
Stream<T> sorted(Comparator<? super T> c); // 自定义顺序
🧠 工作机制:
sorted()是有状态的中间操作。- 它必须读取所有元素后再排序输出。
- 所以它不适用于无限流,会导致挂起或卡死。
✅ 示例 1:自然排序(String 实现了 Comparable)
java
List<String> strings = List.of("one", "two", "three", "four");
List<String> naturalSort = strings.stream()
.sorted()
.toList();
System.out.println("natural sort: " + naturalSort);
🔽 输出:
java
natural sort: [four, one, three, two]
👉 字符串按照字典序排序。
✅ 示例 2:按长度排序(使用 Comparator)
java
List<String> shortestFirst = strings.stream()
.sorted(Comparator.comparingInt(String::length))
.toList();
System.out.println("shortest first: " + shortestFirst);
🔽 输出:
java
shortest first: [one, two, four, three]
👉 按字符串长度升序排序。
🔁 无限流中的表现对比
✅ distinct() 适用于无限流
它可以边处理边输出 ,只要有足够的终止条件(如 limit()),就不会卡死。
示例:
java
var ints = IntStream.iterate(0, i -> i + 1)
.map(i -> i / 3)
.distinct()
.limit(5)
.toArray();
System.out.println("ints = " + Arrays.toString(ints));
🔽 输出:
java
ints = [0, 1, 2, 3, 4]
🌟 它发现新值就会推送给下游,表现非常高效!
❌ sorted() 不适用于无限流
因为它必须先收集所有元素再排序,所以当流无限时,它永远等不到结束,程序会卡死或耗尽内存。
示例(反例):
java
int[] ints = IntStream.iterate(0, i -> i + 1)
.map(i -> i / 3)
.sorted()
.limit(5)
.toArray();
System.out.println("ints = " + Arrays.toString(ints));
⚠️ 输出:
java
<程序将无限等待,永远没有结果>
✅ 总结对比
| 方法 | 是否有状态 | 是否可用于无限流 | 内部机制 |
|---|---|---|---|
distinct() |
有状态 | ✅ 支持 | 使用 Set 去重 |
sorted() |
有状态 | ❌ 不支持 | 使用缓冲区收集+排序 |
🧠 延伸小技巧
🧩 想去重自定义对象?
你的类必须实现正确的:
equals()hashCode()
示例:
java
record User(String name, int age) {}
List<User> users = ...;
users.stream().distinct().toList(); // 正常去重
如用的是 Comparator 排序:
java
users.stream()
.sorted(Comparator.comparing(User::age))
.toList();