java stream操作
Java Stream 操作详解:从基础到实战
Java Stream(流)是 Java 8 引入的核心特性,基于函数式编程思想,为集合数据处理提供了简洁、高效的链式操作。Stream 不存储数据,也不修改源集合,而是通过一系列中间操作(惰性求值)和终止操作(触发执行),实现对数据的过滤、转换、聚合等处理。本文将全面解析 Stream 的核心概念、操作方法及最佳实践。
Stream 核心概念
什么是 Stream?
Stream 是 “数据流” 的抽象,可将集合(如 List、Set)转换为流,通过链式操作对元素进行处理。其核心特点:
- 不存储数据:流只是数据的 “视图”,操作不会改变源集合。
- 惰性求值:中间操作仅描述处理逻辑,不实际执行,直到终止操作被调用才一次性执行。
- 一次性使用:流只能被消费一次,执行终止操作后即失效,再次使用需重新创建。
Stream 操作流程
Stream 处理分为三个阶段,形成 “流水线”:
数据源(集合、数组等)→ 中间操作(过滤、转换等)→ 终止操作(聚合、收集等)
- 数据源:可是
Collection(通过stream()或parallelStream())、数组(Arrays.stream())、生成器(Stream.generate())等。 - 中间操作:对数据进行处理(如筛选、排序),返回新的流,可链式调用。
- 终止操作:触发流的执行,返回非流结果(如集合、数值、布尔值等)。
Stream 的创建(生成流)
从集合创建
所有 Collection 接口的实现类(如 List、Set)均可通过 stream() 或 parallelStream() 生成流:
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 顺序流(单线程处理)
Stream<String> parallelStream = list.parallelStream(); // 并行流(多线程处理)
从数组创建
通过 Arrays.stream() 从数组生成流:
String[] arr = {"x", "y", "z"};
Stream<String> stream = Arrays.stream(arr);
// 也可指定数组范围(从索引1到3,左闭右开)
Stream<String> rangeStream = Arrays.stream(arr, 1, 3); // 包含arr[1],不包含arr[3]
直接生成流
通过 Stream 静态方法直接生成流:
| 方法 | 功能描述 | 示例 |
|---|---|---|
Stream.of(T...) |
从可变参数生成流 | Stream.of("hello", "world") |
Stream.empty() |
生成空流 | Stream<Object> empty = Stream.empty(); |
Stream.generate(Supplier<T>) |
生成无限流(需配合 limit 限制长度) |
Stream.generate(() -> "echo").limit(5) |
Stream.iterate(T, UnaryOperator<T>) |
生成无限迭代流 | Stream.iterate(1, n -> n + 2).limit(3) // 1,3,5 |
中间操作:描述处理逻辑
中间操作返回新的 Stream,可链式调用,仅在终止操作触发时才执行。常见中间操作如下:
筛选与切片
(1)filter(Predicate<T>):过滤元素
保留满足 Predicate 条件(返回 true)的元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 筛选偶数
Stream<Integer> evenStream = numbers.stream().filter(n -> n % 2 == 0);
// 终止操作:打印结果(2,4)
evenStream.forEach(System.out::println);
(2)distinct():去重
基于元素的 equals() 和 hashCode() 去重:
List<String> words = Arrays.asList("a", "a", "b", "c", "c");
// 去重后:a,b,c
words.stream().distinct().forEach(System.out::print);
(3)limit(long n):截断流
保留前 n 个元素:
// 保留前3个元素:1,2,3
Stream.iterate(1, n -> n + 1).limit(3).forEach(System.out::print);
(4)skip(long n):跳过元素
跳过前 n 个元素(若流长度小于 n,返回空流):
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
// 跳过前2个,保留:3,4
nums.stream().skip(2).forEach(System.out::print);
注意:limit 和 skip 顺序不同,结果不同:
// 先limit(2)再skip(1):保留第2个元素(2)
nums.stream().limit(2).skip(1).forEach(System.out::print);
// 先skip(1)再limit(2):保留第2、3个元素(2,3)
nums.stream().skip(1).limit(2).forEach(System.out::print);
排序
(1)sorted():自然排序
元素需实现 Comparable 接口(如 Integer、String):
List<String> strs = Arrays.asList("c", "a", "b");
// 自然排序(按字母顺序):a,b,c
strs.stream().sorted().forEach(System.out::print);
(2)sorted(Comparator<T>):定制排序
通过 Comparator 自定义排序逻辑:
List<User> users = Arrays.asList(
new User("Alice", 20),
new User("Bob", 18)
);
// 按年龄升序排序(18→20)
users.stream()
.sorted(Comparator.comparingInt(User::getAge))
.forEach(u -> System.out.println(u.getAge()));
映射:转换元素
(1)map(Function<T, R>):元素转换
将流中元素通过 Function 转换为另一种类型:
List<String> words = Arrays.asList("hello", "world");
// 将字符串转换为长度:5,5
words.stream()
.map(String::length)
.forEach(System.out::print);
(2)flatMap(Function<T, Stream<R>>):流合并
将每个元素转换为一个流,再合并为单个流(解决 map 可能产生的 “流中流” 问题):
List<String> list = Arrays.asList("abc", "def");
// 需求:将每个字符串拆分为字符,最终得到 [a,b,c,d,e,f]
// 若用map:得到 Stream<Stream<Character>>(流中流)
Stream<Stream<Character>> mapStream = list.stream()
.map(s -> {
List<Character> chars = new ArrayList<>();
for (char c : s.toCharArray()) chars.add(c);
return chars.stream();
});
// 用flatMap:合并为单个流
Stream<Character> flatMapStream = list.stream()
.flatMap(s -> {
List<Character> chars = new ArrayList<>();
for (char c : s.toCharArray()) chars.add(c);
return chars.stream();
});
flatMapStream.forEach(System.out::print); // abcdef
map vs flatMap:
map:T → R(每个元素转换为单个对象)。flatMap:T → Stream<R>(每个元素转换为流,再合并)。
终止操作:触发执行并返回结果
终止操作触发中间操作的执行,并返回非流结果(如集合、数值、布尔值等)。
匹配与查找
(1)allMatch(Predicate<T>):全匹配
检查是否所有元素都满足条件:
List<Integer> nums = Arrays.asList(2, 4, 6);
// 判断是否全为偶数(true)
boolean allEven = nums.stream().allMatch(n -> n % 2 == 0);
(2)anyMatch(Predicate<T>):任一匹配
检查是否至少一个元素满足条件:
List<String> words = Arrays.asList("a", "b", "");
// 判断是否存在空字符串(true)
boolean hasEmpty = words.stream().anyMatch(s -> s.isEmpty());
(3)noneMatch(Predicate<T>):全不匹配
检查是否所有元素都不满足条件:
List<Integer> nums = Arrays.asList(1, 3, 5);
// 判断是否全不是偶数(true)
boolean noneEven = nums.stream().noneMatch(n -> n % 2 == 0);
(4)findFirst():获取第一个元素
返回流中第一个元素(Optional<T> 类型,避免空指针):
List<String> words = Arrays.asList("a", "b", "c");
// 获取第一个元素("a")
Optional<String> first = words.stream().findFirst();
first.ifPresent(System.out::print); // 若存在则打印
(5)findAny():获取任一元素
返回流中任意一个元素(并行流中可能非第一个,顺序流中通常是第一个):
List<Integer> nums = Arrays.asList(1, 2, 3);
// 并行流中可能返回任意元素(如2)
Optional<Integer> any = nums.parallelStream().findAny();
(6)count():计数
返回流中元素的数量:
long count = Arrays.asList(1, 2, 3).stream().count(); // 3
(7)max(Comparator<T>) / min(Comparator<T>):最值
返回流中按比较器排序后的最大值/ 最小值:
List<Integer> nums = Arrays.asList(3, 1, 2);
// 最大值(3)
Optional<Integer> max = nums.stream().max(Integer::compare);
// 最小值(1)
Optional<Integer> min = nums.stream().min(Integer::compare);
归约(reduce):聚合为单个值
reduce 用于将流中元素通过二元运算聚合为一个值,有两种常用形式:
(1)无初始值:reduce(BinaryOperator<T>)
返回 Optional<T>(流可能为空):
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
// 求和:1+2+3+4=10
Optional<Integer> sum = nums.stream().reduce((a, b) -> a + b);
sum.ifPresent(System.out::println);
(2)有初始值:reduce(T identity, BinaryOperator<T>)
以 identity 为初始值,返回 T(非 Optional,因初始值保证结果非空):
// 初始值为0,求和:0+1+2+3+4=10
int sum = nums.stream().reduce(0, (a, b) -> a + b);
注:
count()、max()、min()等方法底层均基于reduce实现。
收集(collect):转换为集合或其他类型
collect(Collector) 是最常用的终止操作,通过 Collectors 工具类提供的收集器,将流转换为 List、Set、Map 等,或进行分组、聚合等操作。
(1)基本收集:转集合
List<String> list = Arrays.asList("a", "b");
// 转为List(默认ArrayList)
List<String> resultList = list.stream().collect(Collectors.toList());
// 转为Set(默认HashSet)
Set<String> resultSet = list.stream().collect(Collectors.toSet());
// 转为指定集合(如LinkedList)
LinkedList<String> linkedList = list.stream()
.collect(Collectors.toCollection(LinkedList::new));
(2)数据汇总
Collectors 提供了多种汇总工具,适用于数值型元素:
| 方法 | 功能 | 示例 |
|---|---|---|
summingInt(ToIntFunction) |
求和 | summingInt(User::getAge) |
averagingInt(ToIntFunction) |
求平均值 | averagingInt(User::getAge) |
summarizingInt(ToIntFunction) |
汇总(计数、总和、平均值等) | summarizingInt(User::getAge) |
示例:
List<User> users = Arrays.asList(
new User("A", 20),
new User("B", 30)
);
// 年龄总和:50
int totalAge = users.stream().collect(Collectors.summingInt(User::getAge));
// 年龄平均值:25.0
double avgAge = users.stream().collect(Collectors.averagingInt(User::getAge));
// 汇总信息:count=2, sum=50, min=20, average=25.0, max=30
IntSummaryStatistics stats = users.stream()
.collect(Collectors.summarizingInt(User::getAge));
(3)字符串连接
joining 用于拼接流中字符串元素:
List<String> words = Arrays.asList("Hello", "World");
// 直接拼接:HelloWorld
String joined = words.stream().collect(Collectors.joining());
// 用分隔符拼接:Hello,World
String joinedWithDelimiter = words.stream().collect(Collectors.joining(","));
// 带前缀、后缀和分隔符:[Hello,World]
String joinedWithAll = words.stream().collect(Collectors.joining(",", "[", "]"));
(4)分组(groupingBy)
将元素按指定条件分组,返回 Map<分组键, List<元素>>:
List<User> users = Arrays.asList(
new User("A", 20, "男"),
new User("B", 22, "女"),
new User("C", 20, "男")
);
// 按性别分组:{男: [A,C], 女: [B]}
Map<String, List<User>> groupByGender = users.stream()
.collect(Collectors.groupingBy(User::getGender));
// 多级分组:先按性别,再按年龄
Map<String, Map<Integer, List<User>>> groupByGenderAndAge = users.stream()
.collect(Collectors.groupingBy(
User::getGender, // 一级分组键
Collectors.groupingBy(User::getAge) // 二级分组键
));
(5)分区(partitioningBy)
特殊的分组,按 Predicate 结果分为两组(true 和 false):
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
// 按是否偶数分区:{false: [1,3], true: [2,4]}
Map<Boolean, List<Integer>> partitioned = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
Optional:避免空指针的容器
Optional<T> 是 Stream 终止操作的常见返回类型,用于封装可能为 null 的值,避免直接操作 null 导致的 NullPointerException。
核心方法
| 方法 | 功能描述 |
|---|---|
Optional.of(T) |
封装非 null 值(值为 null 则抛异常) |
Optional.ofNullable(T) |
封装可能为 null 的值(安全) |
optional.isPresent() |
判断值是否存在(不推荐,类似 obj != null) |
optional.ifPresent(Consumer<T>) |
若值存在,执行 Consumer 操作 |
optional.orElse(T) |
若值不存在,返回默认值 T |
optional.orElseGet(Supplier<T>) |
若值不存在,通过 Supplier 生成默认值 |
optional.orElseThrow(Supplier<Exception>) |
若值不存在,抛出 Supplier 生成的异常 |
最佳实践
避免使用 isPresent() + get()(与 null 判断无差异),应使用函数式方法:
// 反例:低效且与 null 判断无异
Optional<String> opt = Optional.ofNullable(getPossiblyNullValue());
if (opt.isPresent()) {
System.out.println(opt.get());
} else {
System.out.println("默认值");
}
// 正例1:ifPresent 处理存在的情况
opt.ifPresent(System.out::println);
// 正例2:orElse 提供默认值
String result = opt.orElse("默认值");
// 正例3:orElseGet 延迟生成默认值(更高效,默认值生成耗时场景)
String result = opt.orElseGet(() -> generateDefaultValue());
// 正例4:值不存在时抛自定义异常
String result = opt.orElseThrow(() -> new IllegalArgumentException("值不存在"));