Java Stream 详尽篇
1.Stream API 介绍
Java8 版本引入的重要特性之一, 对于集合(例如 List、Map、Set 等)进行处理和操作的高级抽象.Stream API 提供了一种更为简洁高效的的方式来处理集合数据, 可读性较高, 特别在数据的过滤、转换、聚合时的操作很简单方便快捷.
1.1 特点与优势
- 函数式风格: Stream API 使用函数式接口和 Lambda 表达式, 让代码更具表达力和简洁性.
- 惰性求值: Stream 操作通常是惰性求值的, 这意味着在终端操作之前, 中间操作不会立即执行, 从而提高了效率.
- 并行处理: 通过 Stream API 可以方便地实现并行处理, 从而充分利用多核处理器的优势, 加速处理大量数据的过程.
下面根据一些实际应用场景来分析
1.2 两大类型
- 中间操作(Intermediate Operations):中间操作是指在 Stream 上执行的操作, 它们返回一个新的 Stream, 允许你链式地进行多个中间操作.
- 终端操作(Terminal Operations):对 Stream 进行最终处理的操作, 当调用终端操作时, Stream 会开始执行中间操作, 并生成最终的结果或副作用.终端操作是 Stream 的”触发器”, 一旦调用终端操作, Stream 就不能再被使用, 也不能再进行中间操作.
2.Stream API 中间操作
2.1 filter(条件)
用于根据指定条件过滤元素.它接收一个条件作为参数, 只保留满足条件的元素, 并生成一个新的 Stream.
示例:
存在一个姓名的集合,现在要过滤出来名字中带【大】的人员.
1 |
|
输出:
1 |
|
这段 Java 代码展示了如何使用 Java Stream API 对一个字符串列表进行处理,并筛选出包含字符”大”的字符串,然后将筛选后的结果收集到一个新的列表中,最后输出结果。
让我们逐步解释这段代码:
首先我们创建了一个包含四个字符串的集合,后使用 Stream API 对 tempList 进行处理。
首先,通过调用 stream()方法,将 tempList 转换为一个 Stream。
接着,使用 filter 中间操作筛选出包含字符”大”的字符串。
这里的 filter 方法接收一个 Lambda 表达式作为参数,Lambda 表达式s -> s.contains("大")
用于判断字符串是否包含字符”大”。
只有满足条件的字符串会被保留在 Stream 中。
collect(Collectors.toList())这部分是终端操作,它将 Stream 中的元素收集到一个新的列表中。在这里,我们使用 Collectors.toList()方法来将 Stream 中的筛选结果收集到一个新的 List 中,赋值给 resList。
2.2 map(函数)
用于对每个元素执行映射操作, 将元素转换成另一种类型.它接收一个 Function(映射函数)作为参数, 对每个元素应用该映射函数, 并生成一个新的 Stream.
示例:
存在一个姓名的集合,现在要给所有名字加上姓名的前缀.
1 |
|
输出:
1 |
|
这段代码的意思是创建一个姓名集合,通过 stream()方法把集合转换为 Stream 流,通过 map() 方法给每个集合中的值拼接字符串后,使用 collect()方法把这些元素归集到一个新的 List 集合并赋值给 resList.
这里的
.map(s -> "姓名: " + s)
是简写,详细的更容易理解的写法如下:.map(s -> {
return “姓名: “ + s;
})
2.3 flatMap(函数)
类似于 map 操作,但是 flatMap 操作可以将每个元素映射成一个 Stream,然后把所有生成的 Stream 合并成一个新的 Stream。
示例:
新建一个静态内部类, 然后聚合类中的集合数据
1 |
|
Tips: 就现在想要把 List<Personnel>
中的 tagList
聚合后进行处理, 代码如下:
1 |
|
输出:
1 |
|
2.4 sorted()
用于对 Stream 中的元素进行排序,默认按照自然顺序进行排序。也可以传入自定义的 Comparator 来指定排序规则。
示例:
1 |
|
输出:
1 |
|
关于
return y.compareTo(x);
这块的具体意义,可以自行搜索compareTo
方法了解下,这里不做单独赘述
2.5 distinct()
用于去除 Stream 中重复的元素,确保最终的 Stream 中每个元素都是唯一的。
示例:
1 |
|
输出:
1 |
|
这段代码先创建了一个数字集合,接下来使用 stream() 方法转化为 Stream 流
使用 distinct() 方法对流中的元素进行去重,返回一个不包含重复元素的 Stream 流
最后使用 collect() 方法对去重后的流转换成一个新的 List 集合。
2.6 limit(long n)
用于限制 Stream 的大小,返回一个最大包含前 n 个元素的新 Stream。
示例:
1 |
|
这段代码创建一个包含整数的 List 集合numList
,使用 Stream API 的limit(4)
方法将集合截取为只包含前 4 个元素的新 List 集合,并将结果输出到控制台。
输出结果为:[1, 2, 3, 4]
2.7 skip(long n)
用于跳过 Stream 中的前 n 个元素,返回一个丢弃了前 n 个元素后剩余元素的新 Stream。
示例:
1 |
|
这段代码创建一个包含整数的 List 集合numList
,使用 Stream API 的 skip
方法将集合截取跳过前 集合大小-2
个元素的新 List 集合,并将结果输出到控制台。
输出结果为:[7, 8]
2.8 peek(Consumer)
用于对每个元素执行一个操作,同时保持 Stream 的流。它可以用于调试或记录 Stream 中的元素。
示例:
1 |
|
输出:
1 |
|
这段代码创建一个包含整数的 List 集合numList
,使用 Stream API 的 peek
方法记录初始的值,通过中间操作 filter 方法过滤值为 5 的后,再次通过 peek 方法来打印并验证我们的表达式后,把过滤后的结果通过 collect 方法生成一个新的 List 集合。
3.终端操作
在 Java Stream API 中,终端操作(Terminal Operations)是对 Stream 进行最终处理的操作。当调用终端操作时,Stream 会开始执行中间操作,并生成最终的结果或副作用。终端操作是 Stream 的触发器
,一旦调用终端操作,Stream 就不能再被使用,也不能再进行中间操作。
以下是一些常见的 Stream API 终端操作:
3.1 forEach(Consumer)
对 Stream 中的每个元素执行指定的操作,接收一个 Consumer(消费者函数)作为参数。它通常用于对 Stream 中的元素进行输出或执行某些操作,但不会返回任何结果。
示例: 遍历输出中间操作后的集合
1 |
|
以上代码的含义是,创建一个工资集合,通过 stream() 方法转换为 Stream 流,通过中间操作 peek() 方法记录转换前的元素值,后通过 map() 方法给元素进行转换操作,最后通过终端操作 forEach() 方法进行遍历.
输出:
1 |
|
3.2 collect(Collector)
用于将 Stream 中的元素收集到一个容器中,接收一个 Collector(收集器)作为参数。它允许你在 Stream 中执行各种集合操作,例如将元素收集到 List、Set、Map 等容器中。
示例: 把 User 实体集合转换为 Map 集合,名字作为 key,工资作为 Name
1 |
|
上述代码的含义是创建一个人员集合,通过 stream() 转换为 Stream 流,使用 collect() 方法把元素归集, 利用 Collectors.toMap() 收集器转换为 Map 后,内部接收会遍历每个元素,
Collectors.toMap(User::getName, User::getSalary)
是简写,详细的写法如下:Collectors.toMap(s -> s.getName(), s -> s.getSalary())
输出:
1 |
|
3.3 toArray()
将 Stream 中的元素转换成一个数组。返回一个包含所有元素的数组,返回的数组类型是根据流元素的类型自动推断的。如果流是空的,将返回一个长度为 0 的数组。
示例:
1 |
|
输出:
1 |
|
在这个示例中,我们创建了一个整数流
IntStream
,然后使用toArray()
方法将流中的所有整数收集到一个数组中,并输出结果数组。
3.4 reduce(BinaryOperator)
Stream
类的 reduce()
方法是用于将流中的元素进行归约操作的方法。接收一个 BinaryOperator(二元运算函数
作为参数,用于对两个元素进行操作,并返回一个合并后的结果。它可以将流中的所有元素按照指定的规则进行合并,并返回一个 Optional
对象,因为流可能为空。
示例:
1 |
|
输出:
1 |
|
上面的代码中,我们创建了一个整数流
IntStream
,然后使用reduce()
方法将流中的整数相加得到总和。由于流中有元素,因此reduce()
方法返回包含总和的Optional
对象。我们使用orElse(0)
获取结果总和,防止流为空的情况。最后输出得到的结果总和。
3.5 min(Comparator) / max(Comparator)
Stream
类的 min()
和 max()
方法是用于查找流中的最小值和最大值的终端操作。它们接受一个 Comparator
对象作为参数来确定元素的顺序,并返回一个 Optional
对象,因为流可能为空。
以下是 min()
和 max()
方法的简要解释以及示例代码:
min()
方法:
1 |
|
- 方法签名:
<T> Optional<T> min(Comparator<? super T> comparator)
min()
方法用于查找流中的最小元素。comparator
参数用于确定元素的顺序,以便找到最小值。- 如果流为空,则返回一个空的
Optional
对象。
max()
方法:
1 |
|
- 方法签名:
<T> Optional<T> max(Comparator<? super T> comparator)
max()
方法用于查找流中的最大元素。comparator
参数用于确定元素的顺序,以便找到最大值。- 如果流为空,则返回一个空的
Optional
对象。
示例:假设我们有一个包含整数的流,并且我们想找到其中的最小值和最大值
1 |
|
输出:
1 |
|
上述代码中,我们创建了一个整数流
Stream<Integer>
,然后使用min()
方法找到最小值,并使用max()
方法找到最大值。我们使用Optional
对象来处理可能为空的情况,并输出找到的最小值和最大值。请注意,一旦流被消耗,就不能再次使用它,因此我们在找到最小值后重新创建了一个整数流来找到最大值。
3.6 count()
Stream
类的 count()
方法是用于计算流中元素个数的终端操作。它返回一个 long
类型的值,表示流中的元素数量。count()
方法是一个终端操作,一旦调用该方法,流就被消耗,无法再次使用。
示例: 有一个包含整数的流,并且我们想计算流中的元素个数
1 |
|
输出:
1 |
|
上述代码中,我们创建了一个整数流
Stream<Integer>
,然后使用count()
方法计算流中的元素个数。由于这是一个终端操作,一旦调用了count()
方法,流就会被消耗,不能再次使用。我们输出计算得到的元素个数。
3.7 anyMatch(Predicate) / allMatch(Predicate) / noneMatch(Predicate)
Stream
类的 anyMatch()
, allMatch()
, 和 noneMatch()
是用于检查流中元素是否满足特定条件的终端操作。它们返回一个布尔值,表示流中的元素是否满足指定的条件。这些方法在遇到满足条件的元素后可能会提前终止流的处理。anyMatch 检查是否有任意元素满足条件,allMatch 检查是否所有元素都满足条件,noneMatch 检查是否没有元素满足条件。
以下是 anyMatch()
, allMatch()
, 和 noneMatch()
方法的简要解释以及示例代码:
anyMatch()
方法:
1 |
|
- 方法签名:
boolean anyMatch(Predicate<? super T> predicate)
anyMatch()
方法用于检查流中是否存在至少一个元素满足给定的条件。- 它接受一个
Predicate
参数,用于定义满足条件的判断规则。 - 如果流中至少有一个元素满足条件,返回
true
,否则返回false
。
allMatch()
方法:
1 |
|
- 方法签名:
boolean allMatch(Predicate<? super T> predicate)
allMatch()
方法用于检查流中的所有元素是否都满足给定的条件。- 它接受一个
Predicate
参数,用于定义满足条件的判断规则。 - 如果流中的所有元素都满足条件,返回
true
,否则返回false
。
noneMatch()
方法:
1 |
|
- 方法签名:
boolean noneMatch(Predicate<? super T> predicate)
noneMatch()
方法用于检查流中是否所有元素都不满足给定的条件。- 它接受一个
Predicate
参数,用于定义满足条件的判断规则。 - 如果流中的所有元素都不满足条件,返回
true
,否则返回false
。
示例代码:
假设我们有一个包含整数的流,并且我们想检查流中是否存在某些元素满足特定条件。
1 |
|
输出:
1 |
|
上述代码中,我们创建了一个整数流
Stream<Integer>
,然后使用anyMatch()
方法检查是否存在元素大于 5,使用allMatch()
方法检查是否所有元素都小于 10,以及使用noneMatch()
方法检查是否没有元素等于 10。我们输出检查的结果。注意,在每次使用终端操作后,流就被消耗了,不能再次使用,因此在检查不同条件时需要重新创建流。
3.8 findFirst() / findAny()
Stream
类的 findFirst()
和 findAny()
方法用于在流中查找元素的终端操作。它们都返回一个 Optional
对象,表示找到的元素或元素的可能性。在并行流中,findAny()
方法可能更快,因为它不一定要遍历所有元素。在串行 Stream
中,findFirst()
和 findAny()
返回的是相同的元素,在并行Stream
中,findAny()
返回的是最先找到的元素。
以下是 findFirst()
和 findAny()
方法的简要解释以及示例代码:
findFirst()
方法:
1 |
|
- 方法签名:
Optional<T> findFirst()
findFirst()
方法用于在流中查找第一个满足条件的元素。- 它返回一个
Optional
对象,表示找到的元素,如果流为空,则返回一个空的Optional
对象。
findAny()
方法:
1 |
|
- 方法签名:
Optional<T> findAny()
findAny()
方法用于在流中查找任意一个满足条件的元素。- 它返回一个
Optional
对象,表示找到的元素,如果流为空,则返回一个空的Optional
对象。 - 在并行流中,
findAny()
方法可能比findFirst()
方法更快,因为它不一定要遍历所有元素。
示例代码:
假设我们有一个包含整数的流,并且我们想查找其中的某个元素。
1 |
|
输出(注意输出的结果可能因为流的处理顺序不同而有所变化):
1 |
|
上述代码中,创建了整数流
Stream<Integer>
,然后使用findFirst()
方法找到第一个元素,使用findAny()
方法找到任意一个元素,并输出找到的元素。请注意,一旦调用了这些方法,流就被消耗了,不能再次使用。在并行流中,findAny()
方法可能比findFirst()
方法更快,因为它不一定要遍历所有元素。