Java Stream 流详解
什么是 Stream
Stream 是 Java 8 引入的一个重要的新特性,它提供了一种函数式、声明式的方式来处理集合数据。Stream 不是一种数据结构,而是对数据源(集合、数组等)进行高级迭代和聚合操作的抽象。
Stream 操作的特点:
- 不存储数据:Stream 本身不存储元素,只是对数据源进行计算
- 不改变源数据:Stream 操作不会修改原始数据源,而是返回一个新的结果
- 惰性求值:中间操作不会立即执行,只有在遇到终止操作时才会触发计算
- 可消费性:Stream 只能被消费一次,一旦执行了终止操作,Stream 就会关闭
Stream 的获取方式
1. 通过 Collection 获取
单列集合(List、Set 等)可以通过 stream() 或 parallelStream() 方法获取 Stream:
java
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "1", "2", "3");
// 获取顺序流
list.stream().forEach(s -> System.out.println(s));
// 获取并行流(适合大数据量并行处理)
list.parallelStream().forEach(s -> System.out.println(s));
}2. 通过 Map 获取
双列集合无法直接使用 Stream,需要先将 Map 转换为单列集合:
java
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
// 通过 keySet() 获取键的 Stream
map.keySet().stream().forEach(System.out::println);
// 通过 values() 获取值的 Stream
map.values().stream().forEach(System.out::println);
// 通过 entrySet() 获取键值对的 Stream(最常用)
map.entrySet().stream()
.filter(entry -> entry.getValue() > 25)
.forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));
}3. 通过数组获取
java
public static void main(String[] args) {
String[] arr = {"a", "b", "c"};
// 使用 Arrays.stream()
Arrays.stream(arr).forEach(System.out::println);
// 获取数组的部分范围
Arrays.stream(arr, 0, 2).forEach(System.out::println); // 输出 a, b
}4. 通过 Stream 的静态方法获取
java
public static void main(String[] args) {
// 零散数据
Stream.of("a", "b", "c", "d").forEach(System.out::println);
// 空 Stream
Stream.empty();
// 通过 Builder 构建
Stream.Builder<String> builder = Stream.builder();
builder.add("a").add("b").add("c");
Stream<String> stream = builder.build();
}5. 通过生成函数获取
java
public static void main(String[] args) {
// 迭代生成(从 0 开始,每次 +2)
Stream.iterate(0, n -> n + 2)
.limit(5)
.forEach(System.out::println); // 0, 2, 4, 6, 8
// 生成无限随机数
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
// IntStream 范围生成(包含起始值,不包含结束值)
IntStream.range(1, 5).forEach(System.out::println); // 1, 2, 3, 4
IntStream.rangeClosed(1, 5).forEach(System.out::println); // 1, 2, 3, 4, 5
}Stream 的中间操作
中间操作返回一个新的 Stream,可以链式调用。这些操作是惰性的,不会立即执行。
筛选与切片
java
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// filter:按条件过滤
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println); // 2, 4, 6, 8, 10
// limit:截断流,只取前 N 个元素
numbers.stream()
.limit(3)
.forEach(System.out::println); // 1, 2, 3
// skip:跳过前 N 个元素
numbers.stream()
.skip(5)
.forEach(System.out::println); // 6, 7, 8, 9, 10
// distinct:去重(基于 equals 和 hashCode)
Arrays.asList(1, 2, 2, 3, 3, 3).stream()
.distinct()
.forEach(System.out::println); // 1, 2, 3
}映射
java
public static void main(String[] args) {
List<String> names = Arrays.asList("alice", "bob", "charlie");
// map:将每个元素转换为另一种类型
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // ALICE, BOB, CHARLIE
// mapToInt / mapToLong / mapToDouble:转换为基本类型流
List<String> numStrs = Arrays.asList("1", "2", "3");
int sum = numStrs.stream()
.mapToInt(Integer::parseInt)
.sum(); // 6
// flatMap:将每个元素转换为 Stream,然后扁平化为一个 Stream
List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
nestedList.stream()
.flatMap(List::stream)
.forEach(System.out::println); // 1, 2, 3, 4, 5, 6
}排序
java
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
// sorted():自然排序(元素需实现 Comparable)
numbers.stream()
.sorted()
.forEach(System.out::println); // 1, 1, 2, 3, 4, 5, 6, 9
// sorted(Comparator):自定义排序
numbers.stream()
.sorted((a, b) -> b - a) // 降序
.forEach(System.out::println); // 9, 6, 5, 4, 3, 2, 1, 1
// 更简洁的 Comparator 写法
numbers.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
}peek:调试与观察
java
public static void main(String[] args) {
// peek 通常用于调试,可以查看流中的元素而不影响处理
List<Integer> result = Arrays.asList(1, 2, 3, 4, 5).stream()
.peek(n -> System.out.println("过滤前: " + n))
.filter(n -> n > 2)
.peek(n -> System.out.println("过滤后: " + n))
.map(n -> n * 10)
.peek(n -> System.out.println("映射后: " + n))
.collect(Collectors.toList());
// 输出: 过滤前: 1
// 过滤前: 2
// 过滤前: 3
// 过滤后: 3
// 映射后: 30
// ...
}Stream 的终止操作
终止操作会触发实际的计算,并产生结果或副作用。执行后 Stream 将关闭。
遍历与匹配
java
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// forEach:遍历每个元素
numbers.stream().forEach(System.out::println);
// allMatch:是否所有元素都满足条件
boolean allPositive = numbers.stream().allMatch(n -> n > 0); // true
// anyMatch:是否存在元素满足条件
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
// noneMatch:是否没有元素满足条件
boolean noNegative = numbers.stream().noneMatch(n -> n < 0); // true
// findFirst:返回第一个元素(通常与 filter 配合使用)
Optional<Integer> first = numbers.stream().findFirst();
// findAny:返回任意一个元素(并行流时性能更好)
Optional<Integer> any = numbers.stream().findAny();
}聚合计算
java
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// count:元素个数
long count = numbers.stream().count(); // 5
// max / min:最大/最小值(返回 Optional)
Optional<Integer> max = numbers.stream().max(Integer::compareTo); // 5
Optional<Integer> min = numbers.stream().min(Integer::compareTo); // 1
// reduce:归约操作,将流中的元素合并为一个结果
// 求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b); // 15
// 或更简洁的写法
int sum2 = numbers.stream().reduce(0, Integer::sum); // 15
// 无初始值的 reduce(返回 Optional)
Optional<Integer> sumOpt = numbers.stream().reduce(Integer::sum); // 15
// 求乘积
int product = numbers.stream().reduce(1, (a, b) -> a * b); // 120
}收集结果(Collect)
collect 是最强大的终止操作之一,通常与 Collectors 工具类配合使用:
java
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 收集为 List
List<String> list = names.stream().collect(Collectors.toList());
// 收集为 Set
Set<String> set = names.stream().collect(Collectors.toSet());
// 收集为指定类型的集合
LinkedList<String> linkedList = names.stream()
.collect(Collectors.toCollection(LinkedList::new));
// 收集为 Map(需要处理键冲突)
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length,
(existing, replacement) -> existing // 处理键冲突
));
// joining:拼接字符串
String joined = names.stream().collect(Collectors.joining(", ")); // Alice, Bob, Charlie, David, Eve
String joinedWithPrefixSuffix = names.stream()
.collect(Collectors.joining(", ", "[", "]")); // [Alice, Bob, Charlie, David, Eve]
// groupingBy:分组
Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// {3=[Bob, Eve], 5=[Alice, David], 7=[Charlie]}
// partitioningBy:分区(按条件分为两组)
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(name -> name.length() > 3));
// {false=[Bob, Eve], true=[Alice, Charlie, David]}
// 统计信息
IntSummaryStatistics stats = names.stream()
.collect(Collectors.summarizingInt(String::length));
System.out.println(stats.getAverage()); // 平均长度
System.out.println(stats.getMax()); // 最大长度
System.out.println(stats.getCount()); // 元素个数
}并行流(Parallel Stream)
java
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
numbers.add(i);
}
// 获取并行流
long sum = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
// 或将普通流转换为并行流
long sum2 = numbers.stream()
.parallel()
.mapToLong(Integer::longValue)
.sum();
}注意:并行流并非总是更快。以下情况不建议使用并行流:
- 数据量较小(通常小于 10,000)
- 数据源分割成本较高(如 LinkedList、Stream.iterate)
- 需要保证操作顺序(使用
forEachOrdered性能较差)- 涉及线程不安全的操作
常用示例
对象列表处理
java
class Person {
String name;
int age;
String department;
double salary;
// 构造函数、getter...
}
public static void main(String[] args) {
List<Person> employees = Arrays.asList(
new Person("Alice", 30, "IT", 8000),
new Person("Bob", 25, "HR", 5000),
new Person("Charlie", 35, "IT", 12000),
new Person("David", 28, "Finance", 7000)
);
// 1. 按部门分组,计算平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.averagingDouble(Person::getSalary)
));
// 2. 找出每个部门工资最高的人
Map<String, Optional<Person>> topByDept = employees.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.maxBy(Comparator.comparingDouble(Person::getSalary))
));
// 3. 获取年龄大于 28 岁的员工姓名,按工资降序排列
List<String> names = employees.stream()
.filter(p -> p.getAge() > 28)
.sorted(Comparator.comparingDouble(Person::getSalary).reversed())
.map(Person::getName)
.collect(Collectors.toList());
// 4. 获取所有部门名称(去重)
List<String> departments = employees.stream()
.map(Person::getDepartment)
.distinct()
.collect(Collectors.toList());
}总结
| 操作类型 | 常用方法 | 说明 |
|---|---|---|
| 筛选 | filter, distinct, limit, skip | 过滤和截取元素 |
| 映射 | map, flatMap, mapToInt | 转换元素类型或结构 |
| 排序 | sorted | 对元素进行排序 |
| 消费 | forEach, peek | 遍历或观察元素 |
| 匹配 | allMatch, anyMatch, noneMatch | 条件判断 |
| 查找 | findFirst, findAny | 查找元素 |
| 聚合 | reduce, count, max, min | 归约计算 |
| 收集 | collect | 将结果收集到集合中 |