我们是由枫哥组建的IT技术团队,成立于2017年,致力于帮助IT从业者提供实力,成功入职理想企业,我们提供一对一学习辅导,由知名大厂导师指导,分享Java技术、参与项目实战等服务,并为学员定制职业规划,全面提升竞争力,过去8年,我们已成功帮助数千名求职者拿到满意的Offer:IT枫斗者IT枫斗者-Java面试突击


前言

Java8 推出的 Stream API,是 Java 集合编程的重要分水岭,彻底告别了传统繁琐的 for 循环遍历模式,开启了声明式编程时代。

很多人觉得 Stream 只是“代码更短”,但它的核心价值远不止于此:将数据的筛选、转换、分组、聚合等操作,以流水线的方式直观表达,代码语义清晰、可读性极强、易于维护

先通过一组对比,直观感受 Stream 的优势:

传统 For 循环写法

List<String> result = new ArrayList<>();
for (String name : names) {
    if (name.length() > 3) {
        result.add(name.toUpperCase());
    }
}

Stream 流式写法

List<String> result = names.stream()
        .filter(name -> name.length() > 3)
        .map(String::toUpperCase)
        .collect(Collectors.toList());

代码不再是机械的循环逻辑,而是直白描述数据处理规则:筛选、转换、收集,流水线语义一目了然。

一、Stream 到底是什么?(核心原理)

重点结论:Stream 不是集合、不存储数据,它是数据源的一次数据处理流水线,仅负责数据的计算、流转、处理。

一条完整的 Stream 流水线,固定由三部分组成:

List<String> result = names.stream()       // 1. 数据源:创建流
        .filter(name -> name.length() > 3) // 2. 中间操作:链式处理数据
        .map(String::toUpperCase)          // 2. 中间操作:链式处理数据
        .collect(Collectors.toList());     // 3. 终止操作:触发执行、收集结果

核心特性:延迟执行

所有中间操作(filter/map/sorted等)仅做逻辑定义,不会立即执行。只有遇到**终止操作(collect/forEach/count等)**时,整条流水线才会一次性触发执行,极大提升执行效率。

二、Stream 常见创建方式

日常开发中,99% 的场景都是以下 4 种创建方式,简单好记、全覆盖。

1. 集合创建流(最常用)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 普通串行流
Stream<Integer> stream = numbers.stream();
// 并行流(多线程处理,慎用)
Stream<Integer> parallelStream = numbers.parallelStream();

2. 数组创建流

String[] words = {"Java", "Stream", "Lambda"};
Stream<String> stream = Arrays.stream(words);

3. 快速创建少量元素流

Stream<String> stream = Stream.of("Java", "Spring", "MySQL");

4. 创建无限流(需配合 limit 截断)

无限流必须搭配 limit 使用,否则会无限循环执行!

// 生成 1、2、3、4、5... 无限自增序列
Stream<Integer> numbers = Stream.iterate(1, n -> n + 1);
// 截取前5个元素
numbers.limit(5).forEach(System.out::println);

输出结果:1 2 3 4 5

三、Stream 核心中间操作(业务高频)

中间操作特点:返回 Stream 对象,支持链式调用,延迟执行。

1. filter:数据筛选

接收 Predicate 断言,保留返回 true 的元素,过滤不符合条件数据。

// 筛选偶数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());
// 输出:[2, 4, 6]

// 业务场景:筛选成年用户
List<User> adults = users.stream()
        .filter(user -> user.getAge() >= 18)
        .collect(Collectors.toList());

2. map:数据转换(一对一)

接收Function 函数,实现类型转换、字段提取、数据加工,一对一转换元素。

// 字符串转大写
List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> upperNames = names.stream()
        .map(String::toUpperCase)
        .collect(Collectors.toList());

// 业务高频:提取对象字段
List<String> userNameList = users.stream()
        .map(User::getName)
        .collect(Collectors.toList());

3. flatMap:扁平化处理(一对多)

核心区别:map 是一对一转换,flatMap 是一对多+摊平嵌套结构,专门处理二维集合、字符串拆分等嵌套场景。

// 二维列表转一维列表
List<List<String>> groups = Arrays.asList(
        Arrays.asList("Java", "Spring"),
        Arrays.asList("MySQL", "Redis")
);
List<String> skills = groups.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
// 输出:[Java, Spring, MySQL, Redis]

// 字符串拆分摊平
List<String> lines = Arrays.asList("java stream", "spring boot");
List<String> words = lines.stream()
        .flatMap(line -> Arrays.stream(line.split(" ")))
        .collect(Collectors.toList());
// 输出:[java, stream, spring, boot]

4. distinct:元素去重

基本类型/字符串可直接去重,自定义对象去重依赖 equals() 和 hashCode() 方法,未重写会去重失效。

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> result = numbers.stream()
        .distinct()
        .collect(Collectors.toList());
// 输出:[1, 2, 3]

5. sorted:数据排序

分为自然排序和自定义比较器排序,属于有状态中间操作,需要缓存数据,性能低于 filter/map。

// 基本类型自然升序
List<Integer> sortedNumbers = Arrays.asList(5, 1, 3, 2, 4).stream()
        .sorted()
        .collect(Collectors.toList());

// 对象升序排序(按年龄)
List<User> sortedUserAsc = users.stream()
        .sorted(Comparator.comparing(User::getAge))
        .collect(Collectors.toList());

// 对象降序排序
List<User> sortedUserDesc = users.stream()
        .sorted(Comparator.comparing(User::getAge).reversed())
        .collect(Collectors.toList());

6. limit & skip:数据截取

常用于简易内存分页,注意:大数据量分页优先用数据库分页,内存分页性能极差

List<Integer> numbers = Arrays.asList(1,2,3,4,5,6);
// 取前3个元素
List<Integer> firstThree = numbers.stream().limit(3).collect(Collectors.toList());
// 跳过前3个元素
List<Integer> afterThree = numbers.stream().skip(3).collect(Collectors.toList());

// 简易分页示例
int pageNo = 2;
int pageSize = 10;
List<User> pageData = users.stream()
        .skip((long) (pageNo - 1) * pageSize)
        .limit(pageSize)
        .collect(Collectors.toList());

7. peek:调试偷看元素

仅用于开发调试,查看流水线中元素状态,禁止用于业务逻辑处理、修改外部状态

List<String> result = names.stream()
        .filter(name -> name.length() > 3)
        .peek(name -> System.out.println("筛选后元素:" + name))
        .map(String::toUpperCase)
        .collect(Collectors.toList());

四、Stream 核心终止操作(触发执行)

终止操作执行后,Stream 流水线结束,流不可复用。

1. forEach / forEachOrdered:遍历

  • forEach:普通遍历,并行流不保证顺序
  • forEachOrdered:保证遍历顺序,适配并行流
// 普通遍历
names.stream().forEach(System.out::println);
// 并行流有序遍历
names.parallelStream().forEachOrdered(System.out::println);

开发规范:遍历仅用于打印、调用方法等动作,生成新集合优先用 map/filter+collect。

2. collect:结果收集(最核心)

支持收集为 List、Set、Map、分组集合等,适配所有结果场景。

// 收集为 List
List<String> list = names.stream().collect(Collectors.toList());

// 收集为 Set(自动去重)
Set<String> set = names.stream().collect(Collectors.toSet());

// 收集为 Map(避坑:处理key重复)
Map<String, Integer> userMap = users.stream()
        .collect(Collectors.toMap(
                User::getName, 
                User::getAge,
                (oldVal, newVal) -> newVal // key重复保留新值
        ));

3. groupingBy:分组统计(业务神器)

日常业务分组、统计场景必备,支持分组、计数、聚合。

// 按年龄分组
Map<Integer, List<User>> userGroupByAge = users.stream()
        .collect(Collectors.groupingBy(User::getAge));

// 按年龄分组统计人数
Map<Integer, Long> userCountByAge = users.stream()
        .collect(Collectors.groupingBy(
                User::getAge,
                Collectors.counting()
        ));

4. 聚合操作:count、max、min、reduce

// 统计成年用户数量
long adultCount = users.stream().filter(u -> u.getAge() >= 18).count();

// 获取年龄最大的用户(返回Optional,避免空指针)
Optional<User> oldestUser = users.stream()
        .max(Comparator.comparing(User::getAge));

// reduce 归并求和
List<Integer> nums = Arrays.asList(1,2,3,4);
int sum = nums.stream().reduce(0, Integer::sum); // 结果:10

5. 匹配与查询:anyMatch、allMatch、findFirst

具备短路特性,匹配到结果立即终止遍历,性能极高。

// 是否存在成年人
boolean hasAdult = users.stream().anyMatch(u -> u.getAge() >= 18);
// 是否全部成年
boolean allAdult = users.stream().allMatch(u -> u.getAge() >= 18);
// 查找第一个成年人
Optional<User> firstAdult = users.stream()
        .filter(u -> u.getAge() >= 18)
        .findFirst();

五、数值流:IntStream/LongStream/DoubleStream

专门处理基本数据类型,避免装箱拆箱损耗,支持快速数值统计。

// 1-100求和
int sum = IntStream.rangeClosed(1, 100).sum();

// 对象流转数值流求和
int totalAge = users.stream().mapToInt(User::getAge).sum();

// 批量统计:最大、最小、平均值
IntSummaryStatistics stats = users.stream()
        .mapToInt(User::getAge)
        .summaryStatistics();
System.out.println("最大值:" + stats.getMax());
System.out.println("最小值:" + stats.getMin());
System.out.println("平均值:" + stats.getAverage());

六、Stream 延迟执行原理(核心面试点)

Stream 并非分步执行所有操作,而是将所有中间操作组装成一条流水线,单个元素一次性走完所有流程,搭配短路操作极大优化性能。

List<String> result = Arrays.asList("a", "bb", "ccc", "dddd").stream()
        .filter(s -> {
            System.out.println("filter: " + s);
            return s.length() > 1;
        })
        .map(s -> {
            System.out.println("map: " + s);
            return s.toUpperCase();
        })
        .limit(2)
        .collect(Collectors.toList());

执行输出

filter: a
filter: bb
map: bb
filter: ccc
map: ccc
[BB, CCC]

可以看到:达到 limit(2) 后,后续元素不再处理,完美体现延迟执行+短路优化特性。

七、并行流:不是万能加速器!

并行流通过多线程拆分任务处理,有线程调度成本,并非越快越好

适合并行流的场景

  • 数据量大、单元素处理逻辑耗时
  • 无共享可变状态、线程安全
  • 数据源易拆分(ArrayList、数组、数值区间)

不适合并行流的场景

  • 数据量小(线程调度成本大于计算收益)
  • 强顺序依赖、I/O 密集型任务
  • 存在共享变量修改操作

并行流正确写法(规避线程不安全)

// 错误:并行修改普通集合,线程不安全
List<Integer> badList = new ArrayList<>();
IntStream.rangeClosed(1, 1000).parallel().forEach(badList::add);

// 正确:通过collect收集,线程安全
List<Integer> goodList = IntStream.rangeClosed(1, 1000)
        .parallel()
        .boxed()
        .collect(Collectors.toList());

八、业务实战:订单场景综合案例

基于订单类,实现日常高频统计、筛选、转换业务场景:

// 订单实体类
class Order {
    private String orderNo;
    private String userId;
    private BigDecimal amount;
    private String status;

    // 构造器、getter省略
}

// 1. 统计已支付订单总金额
BigDecimal totalPaid = orders.stream()
        .filter(o -> "PAID".equals(o.getStatus()))
        .map(Order::getAmount)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

// 2. 按用户统计订单数量
Map<String, Long> userOrderCount = orders.stream()
        .collect(Collectors.groupingBy(Order::getUserId, Collectors.counting()));

// 3. 获取金额最高的订单
Optional<Order> maxAmountOrder = orders.stream()
        .max(Comparator.comparing(Order::getAmount));

// 4. 拼接所有订单号
String orderNoStr = orders.stream()
        .map(Order::getOrderNo)
        .collect(Collectors.joining(","));

九、Stream 高频坑点(生产必避)

1. Stream 不可复用

流执行终止操作后即销毁,重复调用直接抛异常。

// 错误示例
Stream<String> stream = names.stream();
stream.count();
stream.collect(Collectors.toList()); // 抛出 IllegalStateException

2. toMap 主键重复报错

未指定合并策略时,key 重复直接抛异常,生产必须加合并策略。

3. 并行流修改共享变量线程不安全

禁止在并行流 forEach 中操作普通集合,统一使用 collect 收集结果。

4. Optional 直接 get 空指针风险

findFirst/max/min 返回 Optional,禁止直接 get(),优先用 ifPresent、orElse。

// 安全写法
users.stream()
    .filter(u -> u.getAge() > 100)
    .findFirst()
    .ifPresent(System.out::println);

十、总结:Stream 核心使用原则

必掌握高频操作

筛选filter、转换map、摊平flatMap、排序sorted、收集collect、分组groupingBy、归并reduce、匹配anyMatch、查询findFirst

核心原则

  1. 中间操作仅定义流程,终止操作才触发执行
  2. Stream 只能消费一次,不可复用
  3. Lambda 尽量无副作用,不修改外部变量
  4. 并行流谨慎使用,拒绝盲目炫技
  5. 代码优先清晰可读,不强行堆砌 Stream

Stream 的核心价值是语义化、简洁化、规范化,熟练使用可以彻底告别冗余 for 循环,让 Java 集合代码更优雅、更易维护!


⭐️推荐:

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐