0%

java stream操作

Java Stream 操作详解:从基础到实战

Java Stream(流)是 Java 8 引入的核心特性,基于函数式编程思想,为集合数据处理提供了简洁、高效的链式操作。Stream 不存储数据,也不修改源集合,而是通过一系列中间操作(惰性求值)和终止操作(触发执行),实现对数据的过滤、转换、聚合等处理。本文将全面解析 Stream 的核心概念、操作方法及最佳实践。

Stream 核心概念

什么是 Stream?

Stream 是 “数据流” 的抽象,可将集合(如 ListSet)转换为流,通过链式操作对元素进行处理。其核心特点:

  • 不存储数据:流只是数据的 “视图”,操作不会改变源集合。
  • 惰性求值:中间操作仅描述处理逻辑,不实际执行,直到终止操作被调用才一次性执行。
  • 一次性使用:流只能被消费一次,执行终止操作后即失效,再次使用需重新创建。

Stream 操作流程

Stream 处理分为三个阶段,形成 “流水线”:

1
数据源(集合、数组等)→ 中间操作(过滤、转换等)→ 终止操作(聚合、收集等)
  • 数据源:可是 Collection(通过 stream()parallelStream())、数组(Arrays.stream())、生成器(Stream.generate())等。
  • 中间操作:对数据进行处理(如筛选、排序),返回新的流,可链式调用。
  • 终止操作:触发流的执行,返回非流结果(如集合、数值、布尔值等)。

Stream 的创建(生成流)

从集合创建

所有 Collection 接口的实现类(如 ListSet)均可通过 stream()parallelStream() 生成流:

1
2
3
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 顺序流(单线程处理)
Stream<String> parallelStream = list.parallelStream(); // 并行流(多线程处理)

从数组创建

通过 Arrays.stream() 从数组生成流:

1
2
3
4
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)的元素:

1
2
3
4
5
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() 去重:

1
2
3
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 个元素:

1
2
// 保留前3个元素:1,2,3
Stream.iterate(1, n -> n + 1).limit(3).forEach(System.out::print);
(4)skip(long n):跳过元素

跳过前 n 个元素(若流长度小于 n,返回空流):

1
2
3
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
// 跳过前2个,保留:3,4
nums.stream().skip(2).forEach(System.out::print);

注意limitskip 顺序不同,结果不同:

1
2
3
4
5
// 先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 接口(如 IntegerString):

1
2
3
List<String> strs = Arrays.asList("c", "a", "b");
// 自然排序(按字母顺序):a,b,c
strs.stream().sorted().forEach(System.out::print);
(2)sorted(Comparator<T>):定制排序

通过 Comparator 自定义排序逻辑:

1
2
3
4
5
6
7
8
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 转换为另一种类型:

1
2
3
4
5
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 可能产生的 “流中流” 问题):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

  • mapT → R(每个元素转换为单个对象)。
  • flatMapT → Stream<R>(每个元素转换为流,再合并)。

终止操作:触发执行并返回结果

终止操作触发中间操作的执行,并返回非流结果(如集合、数值、布尔值等)。

匹配与查找

(1)allMatch(Predicate<T>):全匹配

检查是否所有元素都满足条件:

1
2
3
List<Integer> nums = Arrays.asList(2, 4, 6);
// 判断是否全为偶数(true)
boolean allEven = nums.stream().allMatch(n -> n % 2 == 0);
(2)anyMatch(Predicate<T>):任一匹配

检查是否至少一个元素满足条件:

1
2
3
List<String> words = Arrays.asList("a", "b", "");
// 判断是否存在空字符串(true)
boolean hasEmpty = words.stream().anyMatch(s -> s.isEmpty());
(3)noneMatch(Predicate<T>):全不匹配

检查是否所有元素都不满足条件:

1
2
3
List<Integer> nums = Arrays.asList(1, 3, 5);
// 判断是否全不是偶数(true)
boolean noneEven = nums.stream().noneMatch(n -> n % 2 == 0);
(4)findFirst():获取第一个元素

返回流中第一个元素Optional<T> 类型,避免空指针):

1
2
3
4
List<String> words = Arrays.asList("a", "b", "c");
// 获取第一个元素("a")
Optional<String> first = words.stream().findFirst();
first.ifPresent(System.out::print); // 若存在则打印
(5)findAny():获取任一元素

返回流中任意一个元素(并行流中可能非第一个,顺序流中通常是第一个):

1
2
3
List<Integer> nums = Arrays.asList(1, 2, 3);
// 并行流中可能返回任意元素(如2)
Optional<Integer> any = nums.parallelStream().findAny();
(6)count():计数

返回流中元素的数量

1
long count = Arrays.asList(1, 2, 3).stream().count(); // 3
(7)max(Comparator<T>) / min(Comparator<T>):最值

返回流中按比较器排序后的最大值/ 最小值:

1
2
3
4
5
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>(流可能为空):

1
2
3
4
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,因初始值保证结果非空):

1
2
// 初始值为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 工具类提供的收集器,将流转换为 ListSetMap 等,或进行分组、聚合等操作。

(1)基本收集:转集合
1
2
3
4
5
6
7
8
9
10
11
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)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 用于拼接流中字符串元素:

1
2
3
4
5
6
7
8
9
10
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<元素>>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 结果分为两组(truefalse):

1
2
3
4
5
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 判断无差异),应使用函数式方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 反例:低效且与 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("值不存在"));

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10