> 技术文档 > Java Stream流详解:从入门到精通

Java Stream流详解:从入门到精通

目录

引言:为什么需要Stream流

一、Stream流基础

1.1 什么是Stream流

1.2 Stream流与集合的区别

1.3 Stream流的使用步骤

二、创建Stream流

2.1 从不同数据源创建Stream

2.1.1 从单列集合创建Stream(最常用)

2.1.2 从双列集合创建Stream

2.1.3 从数组创建Stream

2.1.4 从零散数据创建Stream

三、Stream流的中间操作

3.1 筛选和切片操作

3.2 映射操作

3.3 排序操作

3.4 查看操作(peek)

3.5 注意事项

四、Stream流的终结方法

4.1 遍历操作

4.2 计数操作

4.3 收集操作

4.4 匹配操作

4.5 查找操作

4.6 归约操作(reduce)

4.7 最大值和最小值

五、collect收集器详解

5.1 收集到List、Set、Map

5.2 收集并分组

5.3 其他常用收集操作

六、Stream流的实际应用案例

6.1 案例一:过滤和转换

6.2 案例二:统计和聚合

6.3 案例三:排序和限制

七、并行流

7.1 创建并行流

7.2 并行流示例

7.3 并行流注意事项

八、常见问题与注意事项

8.1 Stream的一次性使用

8.2 延迟执行特性

8.3 避免副作用

8.4 调试Stream流

8.5 性能考虑

九、面试常见问题

9.1 Stream的特点是什么?

9.2 Stream流、Collection和数组的区别?

9.3 介绍一下Stream流的工作流程

9.4 什么是中间操作和终端操作?

9.5 并行流和顺序流有什么区别?

十、总结


引言:为什么需要Stream流

想象一下,你需要从一堆水果中挑选出所有红色的、重量超过200克的苹果,并按重量排序取前三个。传统方式需要你写多个循环和条件判断,而使用Stream流,你可以这样做:

List result = apples.stream() .filter(a -> a.getColor().equals(\"红色\")) .filter(a -> a.getWeight() > 200) .sorted((a1, a2) -> a1.getWeight() - a2.getWeight()) .limit(3) .collect(Collectors.toList());

Stream流就像一条传送带,数据放上去后可以经过多个处理站,最终得到你想要的结果,让代码更简洁、可读性更强。

一、Stream流基础

1.1 什么是Stream流

Stream流是Java 8引入的处理集合数据的API,它让我们以声明式(描述要做什么,而不是怎么做)的方式处理数据。

核心特点

  • 声明式编程:更关注做什么,而不是怎么做
  • 支持链式操作:可以像搭积木一样组合多个操作
  • 支持并行处理:轻松切换到并行模式,提高性能
  • 延迟执行:在最后一步(终端操作)才会真正执行

1.2 Stream流与集合的区别

简单对比:

  • 集合就像一个仓库,专注于存储和访问数据
  • Stream就像一条流水线,专注于对数据进行操作
特性 集合 Collection Stream流 存储方式 存储元素 不存储元素 操作时机 立即执行 延迟执行(惰性) 遍历次数 可多次遍历 只能遍历一次 关注点 数据 计算

1.3 Stream流的使用步骤

Stream流的使用分为三个步骤:

  1. 创建Stream流:获取一个数据流
  2. 使用中间方法:对流中的数据进行操作
  3. 使用终结方法:对流水线上的数据进行最终操作
// 一个完整的Stream操作示例List names = Arrays.asList(\"小明\", \"小红\", \"小刚\", \"小华\");List filteredNames = names.stream()  // 1.创建Stream流  .filter(name -> name.length() > 1) // 2.中间操作  .collect(Collectors.toList()); // 3.终端操作

二、创建Stream流

2.1 从不同数据源创建Stream

我们可以从多种数据源创建Stream:

获取方式 方法名 说明 单列集合 default Stream stream() Collection中的默认方法 双列集合 无 无法直接使用stream流 数组 public static Stream stream(T[] array) Arrays工具类中的静态方法 一堆零散数据 public static Stream of(T... values) Stream接口中的静态方法
2.1.1 从单列集合创建Stream(最常用)
// 1.单列集合获取Stream流ArrayList list = new ArrayList();Collections.addAll(list, \"a\", \"b\", \"c\", \"d\", \"e\");// 获取一条流水线,并把集合中的数据放到流水线上Stream stream1 = list.stream();// 使用终结方法打印一下流水线上的所有数据stream1.forEach(new Consumer() { @Override public void accept(String s) { // s:依次表示流水线上的每一个数据 System.out.println(s); }});// 使用Lambda简化list.stream().forEach(s -> System.out.println(s));
2.1.2 从双列集合创建Stream

双列集合没有直接获取Stream流的方法,但可以间接获取:

// 1.创建双列集合HashMap hm = new HashMap();// 2.添加数据hm.put(\"红楼梦\", 111);hm.put(\"西游记\", 222);hm.put(\"水浒传\", 333);hm.put(\"三国演义\", 444);// 3.第一种获取stream流的方式:通过键hm.keySet().stream().forEach(s -> System.out.println(s));// 4.第二种获取stream流的方式:通过值hm.values().stream().forEach(i -> System.out.println(i));// 5.第三种获取stream流的方式:通过键值对hm.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + \"=\" + entry.getValue()));
2.1.3 从数组创建Stream
// 1.创建数组int[] arr1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};String[] arr2 = {\"a\", \"b\", \"c\"};// 2.获取stream流Arrays.stream(arr1).forEach(s -> System.out.println(s));System.out.println(\"========================\");Arrays.stream(arr2).forEach(s -> System.out.println(s));
2.1.4 从零散数据创建Stream
// 一堆零散数据 public static Stream of(T... values) Stream接口中的静态方法Stream.of(1, 2, 3, 4, 5).forEach(s -> System.out.println(s));Stream.of(\"a\", \"b\", \"c\", \"d\", \"e\").forEach(s -> System.out.println(s));

三、Stream流的中间操作

中间操作会返回一个新的Stream,可以链式调用。中间操作不会立即执行,而是等到终端操作时才一起执行。

Stream的中间方法包括:

名称 说明 filter(Predicate predicate) 过滤 limit(long maxSize) 获取前几个元素 skip(long n) 跳过前几个元素 distinct() 元素去重(依赖hashCode和equals方法) concat(Stream a, Stream b) 合并a和b两个流为一个流 map(Function mapper) 转换流中的数据类型

3.1 筛选和切片操作

List numbers = Arrays.asList(1, 2, 2, 3, 4, 5);// filter: 过滤元素List greaterThan3 = numbers.stream() .filter(n -> n > 3) .collect(Collectors.toList());System.out.println(\"大于3的数: \" + greaterThan3); // [4, 5]// distinct: 去除重复List uniqueNumbers = numbers.stream() .distinct() .collect(Collectors.toList());System.out.println(\"去重后: \" + uniqueNumbers); // [1, 2, 3, 4, 5]// limit: 限制数量List firstThree = numbers.stream() .limit(3) .collect(Collectors.toList());System.out.println(\"前三个数: \" + firstThree); // [1, 2, 2]// skip: 跳过元素List skipFirst2 = numbers.stream() .skip(2) .collect(Collectors.toList());System.out.println(\"跳过前两个: \" + skipFirst2); // [2, 3, 4, 5]// 组合使用List result = numbers.stream() .distinct() // 先去重 .skip(1) // 跳过第一个 .limit(3) // 取3个 .collect(Collectors.toList());System.out.println(\"去重后跳过第一个,再取三个: \" + result); // [2, 3, 4]

3.2 映射操作

List words = Arrays.asList(\"Java\", \"Stream\", \"API\");// map: 转换元素List wordLengths = words.stream() .map(word -> word.length()) // 获取每个单词的长度 .collect(Collectors.toList());System.out.println(\"单词长度: \" + wordLengths); // [4, 6, 3]// map的另一个示例:转换大写List upperCaseWords = words.stream()  .map(word -> word.toUpperCase())  .collect(Collectors.toList());System.out.println(\"转换为大写: \" + upperCaseWords); // [JAVA, STREAM, API]// flatMap: 扁平化处理(将多个流合并为一个流)List<List> nestedList = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4, 5));List flatList = nestedList.stream()  .flatMap(list -> list.stream()) // 将每个内部List转为Stream然后合并