Stream流操作
什么,你还不会Stream操作?不急,我们一步一步来学习Stream流吧。
1.Stream介绍
-
Stream(流) 是 Java 8 引入的核心特性之一,它专注于对集合(或其他数据源)进行高效的聚合操作(如过滤、映射、排序、统计等),支持函数式编程风格,让代码更简洁、可读性更强。
简单说,Stream 的作用是:用声明式的方式(“做什么” 而非 “怎么做”)处理集合数据,替代传统的
for
循环遍历,简化代码。
2.Stream特点
-
不改变数据源:所有操作都不会修改原始集合,而是返回新的流或结果。
-
惰性执行:中间操作(如过滤、映射)不会立即执行,只有当触发 “终止操作” 时才会一次性执行,提高效率。
-
一次性使用:一个流只能被消费一次(执行终止操作后,流就关闭了,不能再复用)。
-
支持并行处理:通过
parallelStream()
可轻松实现并行操作,利用多核 CPU 提高处理大数据的效率。
3.Stream流程操作
使用 Stream 通常分为 3 个步骤:
-
创建流:从数据源(集合、数组等)获取流;
-
中间操作:对数据进行处理(如过滤、转换、排序等),返回新的流;
-
终止操作:触发实际计算,返回处理结果(如列表、统计值等)。
4.创建Stream流
-
从集合(Collection)创建Stream流
这是最常用的方式,所有实现
Collection
接口的类(如List
、Set
)都可以通过stream()
或parallelStream()
方法创建流:-
stream()
:创建串行流(单线程处理) -
parallelStream()
:创建并行流(多线程处理,适合大数据量)import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.HashSet; import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { // 从 List 创建流 List<String> list = Arrays.asList("a", "b", "c"); Stream<String> listStream = list.stream(); // 串行流 Stream<String> parallelListStream = list.parallelStream(); // 并行流 // 从 Set 创建流 Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3)); Stream<Integer> setStream = set.stream(); } }
-
-
从数组创建Stream流
通过
Arrays.stream()
方法可将数组转换为流,支持基本类型数组(如int[]
、double[]
)和对象数组:import java.util.Arrays; import java.util.stream.IntStream; import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { // 对象数组 → Stream String[] strArray = {"x", "y", "z"}; Stream<String> strStream = Arrays.stream(strArray); // 基本类型数组 → 对应的流(如 IntStream、DoubleStream) int[] intArray = {10, 20, 30}; IntStream intStream = Arrays.stream(intArray); // 专门处理int的流,更高效 } }
-
直接生成流(
Stream.of
)使用
Stream.of()
可直接传入元素生成流,适合已知具体元素的场景:import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { // 传入多个元素 Stream<String> stream1 = Stream.of("apple", "banana", "orange"); // 传入数组(等价于 Arrays.stream()) Integer[] numArray = {1, 2, 3}; Stream<Integer> stream2 = Stream.of(numArray); } }
-
创建空流(
Stream.empty()
)当需要返回一个 “空流”(而非
null
)时,使用Stream.empty()
,避免空指针异常:import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { Stream<String> emptyStream = Stream.empty(); System.out.println(emptyStream.count()); // 输出:0(空流的元素数量为0) } }
-
生成无限流(
generate()
和iterate()
)通过
Stream.generate()
或Stream.iterate()
可创建无限流(元素数量无限),需配合limit(n)
限制元素数量:-
Stream.generate(Supplier)
:通过 Supplier 接口生成元素(每次调用生成一个新元素) -
Stream.iterate(初始值, UnaryOperator)
:从初始值开始,通过迭代函数生成下一个元素
import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { // 1. generate():生成5个随机数(DoubleStream) Stream<Double> randomStream = Stream.generate(Math::random) .limit(5); // 限制为5个元素 randomStream.forEach(System.out::println); // 输出5个0-1之间的随机数 // 2. iterate():生成10以内的奇数(1,3,5,7,9) Stream<Integer> oddStream = Stream.iterate(1, n -> n + 2) .limit(5); // 限制5个元素 oddStream.forEach(System.out::println); // 输出:1 3 5 7 9 } }
-
-
从文件生成流(JDK8+)
通过
Files.lines()
可将文件内容按行转换为流(每行作为一个元素):import java.nio.file.Files; import java.nio.file.Paths; import java.util.stream.Stream; public class CreateStream { public static void main(String[] args) { // 从文件读取行,生成流(需处理异常) try (Stream<String> fileStream = Files.lines(Paths.get("test.txt"))) { fileStream.forEach(System.out::println); // 打印文件每行内容 } catch (Exception e) { e.printStackTrace(); } } }
5.Stream中间操作
Stream 流的中间操作(Intermediate Operations) 是对数据流进行处理和转换的操作,它们的特点是惰性执行(仅定义操作规则,不立即执行),且返回一个新的流,因此可以链式调用多个中间操作。
-
过滤(
filter()
)-
作用:保留满足条件的元素(通过
Predicate
函数判断)。 -
示例:过滤出长度大于 3 的字符串
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamIntermediate { public static void main(String[] args) { List<String> words = Arrays.asList("a", "bb", "ccc", "dddd", "eeeee"); // 过滤出长度 > 3 的元素 List<String> result = words.stream() .filter(word -> word.length() > 3) // 中间操作:过滤 .collect(Collectors.toList()); // 终止操作:收集结果 System.out.println(result); // 输出:[dddd, eeeee] } }
-
-
映射(
map()
)-
作用:将元素转换为另一种类型(通过
Function
函数映射)。 -
示例:将字符串转换为其长度
List<String> words = Arrays.asList("apple", "banana", "cat"); // 字符串 → 长度(Integer类型) List<Integer> lengths = words.stream() .map(String::length) // 等价于 word -> word.length() .collect(Collectors.toList()); System.out.println(lengths); // 输出:[5, 6, 3]
-
-
扁平映射(
flatMap()
)-
作用:将 “流的流”(如
Stream<List<T>>
)扁平化为单个流(Stream<T>
),常用于处理嵌套集合。 -
示例:将多个列表的元素合并为一个流
List<List<Integer>> nestedList = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4, 5), Arrays.asList(6) ); // 扁平化为单个整数流 List<Integer> flatList = nestedList.stream() .flatMap(List::stream) // 将每个子列表转为流,再合并 .collect(Collectors.toList()); System.out.println(flatList); // 输出:[1, 2, 3, 4, 5, 6]
-
-
排序(
sorted()
)-
作用:对元素进行排序,有两种形式:
-
sorted()
:按元素自然顺序排序(需实现Comparable
接口)。 -
sorted(Comparator)
:按自定义比较器排序。
-
-
示例:对整数和字符串排序
// 自然排序(整数) List<Integer> nums = Arrays.asList(3, 1, 4, 2); List<Integer> sortedNums = nums.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNums); // 输出:[1, 2, 3, 4] // 自定义排序(字符串按长度) List<String> words = Arrays.asList("banana", "apple", "cat"); List<String> sortedWords = words.stream() .sorted((s1, s2) -> Integer.compare(s1.length(), s2.length())) .collect(Collectors.toList()); System.out.println(sortedWords); // 输出:[cat, apple, banana]
-
-
去重(
distinct()
)-
作用:去除流中重复的元素(基于
equals()
方法判断)。 -
示例:去除重复整数
List<Integer> nums = Arrays.asList(2, 1, 3, 2, 1); List<Integer> distinctNums = nums.stream() .distinct() .collect(Collectors.toList()); System.out.println(distinctNums); // 输出:[2, 1, 3]
-
-
限制(
limit()
)-
作用:截取流的前
n
个元素。 -
示例:取前 3 个元素
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); List<Integer> limited = nums.stream() .limit(3) .collect(Collectors.toList()); System.out.println(limited); // 输出:[1, 2, 3]
-
-
跳过(
skip()
)-
作用:跳过流的前
n
个元素。 -
示例:跳过前 2 个元素
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); List<Integer> skipped = nums.stream() .skip(2) .collect(Collectors.toList()); System.out.println(skipped); // 输出:[3, 4, 5]
-
-
调试(
peek()
)-
作用:对元素执行操作(如打印、日志),但不改变元素本身,常用于调试。
-
示例:打印流中元素的中间状态
List<Integer> nums = Arrays.asList(1, 2, 3); List<Integer> result = nums.stream() .peek(num -> System.out.println("处理前:" + num)) // 调试打印 .map(num -> num * 2) .peek(num -> System.out.println("处理后:" + num)) .collect(Collectors.toList()); // 输出: // 处理前:1 // 处理后:2 // 处理前:2 // 处理后:4 // 处理前:3 // 处理后:6
-
6.Stream终止操作
-
收集结果:将流转换为集合/其他数据结构
这类操作通过
Collectors
工具类提供的方法,将流中的元素收集为List
、Set
、Map
等常见数据结构,是最常用的终止操作之一。-
collect(Collectors.toList())
:收集为Listimport java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamTerminal { public static void main(String[] args) { List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apple"); // 收集所有元素为 List(允许重复) List<String> fruitList = fruits.stream() .filter(f -> f.startsWith("a")) // 中间操作:过滤以"a"开头的水果 .collect(Collectors.toList()); // 终止操作:收集为 List System.out.println(fruitList); // 输出:[apple, apple] } }
-
collect(Collectors.toSet())
:收集为set// 收集为 Set(自动去重) List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apple"); List<String> fruitSet = fruits.stream() .collect(Collectors.toSet()) // 终止操作:收集为 Set .stream().collect(Collectors.toList()); // 转为 List 方便打印 System.out.println(fruitSet); // 输出:[apple, banana, cherry](去重)
-
collect(Collectors.joining())
:拼接为字符串// 拼接流中的字符串(支持自定义分隔符、前缀、后缀) List<String> words = Arrays.asList("Hello", "Stream", "World"); String joined = words.stream() .collect(Collectors.joining(", ", "【", "】")); // 分隔符:, 前缀:【 后缀:】 System.out.println(joined); // 输出:【Hello, Stream, World】
-
-
遍历元素
forEach()
用于遍历流中的每个元素,执行自定义操作(如打印、修改元素属性),无返回值(void),是简单的 “消费型” 终止操作。List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); // 遍历并打印每个元素(配合 Lambda 表达式) nums.stream() .filter(n -> n % 2 == 0) // 中间操作:筛选偶数 .forEach(n -> System.out.print(n + " ")); // 终止操作:遍历打印 // 输出:2 4
-
统计与聚合
这类操作用于对数值型流(如
IntStream
、LongStream
)或普通流中的元素进行统计,返回单个数值(如计数、求和、最值、平均值)。-
count()
:统计元素个数List<String> words = Arrays.asList("a", "bb", "ccc", "dddd"); // 统计长度 > 2 的元素个数 long count = words.stream() .filter(w -> w.length() > 2) .count(); // 终止操作:返回 long 类型的计数 System.out.println(count); // 输出:2("ccc" 和 "dddd")
-
max(Comparator)/min(Comparator)
:获取最值List<Integer> scores = Arrays.asList(85, 92, 78, 95, 88); // 获取最高分(max) Integer maxScore = scores.stream() .max(Integer::compare) // 终止操作:按自然顺序取最大值(返回 Optional) .orElse(0); // 若流为空,返回默认值 0 // 获取最低分(min) Integer minScore = scores.stream() .min(Integer::compare) .orElse(0); System.out.println("最高分:" + maxScore); // 输出:95 System.out.println("最低分:" + minScore); // 输出:78
-
sum()/average()
:求和与平均数(仅数值流)List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); // 求和(先转为 IntStream) int sum = nums.stream() .mapToInt(Integer::intValue) // 转为 IntStream .sum(); // 终止操作:求和 // 求平均值(返回 OptionalDouble,避免空流无均值的问题) double avg = nums.stream() .mapToInt(Integer::intValue) .average() .orElse(0.0); System.out.println("总和:" + sum); // 输出:15 System.out.println("平均值:" + avg); // 输出:3.0
-
-
判断与匹配
这类操作用于判断流中元素是否满足特定条件,返回
boolean
类型结果,支持 “存在匹配”“全部匹配”“无匹配” 三种场景。-
anyMatch(Predicate)
:是否存在一个元素满足条件List<String> fruits = Arrays.asList("apple", "banana", "cherry"); // 判断是否存在以"b"开头的水果 boolean hasB = fruits.stream() .anyMatch(f -> f.startsWith("b")); // 终止操作:存在匹配则返回 true System.out.println(hasB); // 输出:true("banana" 满足)
-
allMatch(Predicate)
:是否存在一个元素满足条件// 判断所有水果的长度是否 > 3 boolean allLong = fruits.stream() .allMatch(f -> f.length() > 3); System.out.println(allLong); // 输出:false("apple" 长度 5>3,但 "cherry" 长度 6>3?不,这里例子中 fruits 是 ["apple","banana","cherry"],三者长度都>3?修正:若改为 ["apple","b","cherry"],则输出 false)
-
noneMatch(Predicate)
:是否存在一个元素满足条件// 判断是否没有以"x"开头的水果 boolean noX = fruits.stream() .noneMatch(f -> f.startsWith("x")); System.out.println(noX); // 输出:true(没有以"x"开头的水果)
-
-
获取单个元素
用于从流中获取单个元素,返回
Optional<T>
类型(避免空指针异常,需处理 “无元素” 的情况)。-
findFirst()
:获取流中的第一个元List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5); // 获取第一个偶数 Integer firstEven = nums.stream() .filter(n -> n % 2 == 0) .findFirst() // 终止操作:返回第一个元素的 Optional .orElse(-1); System.out.println(firstEven); // 输出:2
-
findAny()
:获取流中的任意一个元素(并行流中更高效)// 并行流中获取任意一个偶数(串行流中通常也是第一个,但并行流可能返回任意一个) Integer anyEven = nums.parallelStream() .filter(n -> n % 2 == 0) .findAny() .orElse(-1); System.out.println(anyEven); // 输出:2 或 4(并行流中结果可能不同)
-