Java Stream流常用方法实战指南

 更新时间:2026年01月20日 10:21:34   作者:madao哦  
Stream流是Java 8中的一个新特性,它提供了一种处理集合和数组的方式,Stream流可以让我们以一种更加简洁、高效、可读性更强的方式来处理数据,这篇文章主要介绍了Java Stream流常用方法的相关资料,需要的朋友可以参考下

前言

在 Java 8 引入的众多特性中,Stream API 无疑是最能提升代码优雅度和开发效率的工具之一。它让我们能以声明式的方式处理数据集合(类似于 SQL 语句),告别了繁琐的 for 循环和复杂的逻辑判断。

本文将基于实战角度,带你全面掌握 Stream 流的创建中间操作终结操作以及方法引用的高级用法。

一、 不可变集合

在开始学习 Stream 之前,了解 JDK 9+ 引入的不可变集合工厂方法非常有用,它们常被用来快速创建测试数据。

  • List 和 SetList.of() 和 Set.of(),形参依次传入 value。

  • MapMap.of(),形参依次传入 key 和 value。

// 示例
List<String> list = List.of("张三", "李四", "王五");
Set<String> set = Set.of("A", "B", "C");
Map<String, Integer> map = Map.of("张三", 18, "李四", 20);

二、 第一步:获取 Stream 流

Stream 流的生命周期始于“数据源”。不同的数据结构获取流的方式略有不同。

1. 单列集合 (List, Set)

直接调用集合的 .stream() 方法。

ArrayList<String> list = new ArrayList<>();
Stream<String> stream = list.stream();

2. 双列集合 (Map)

Map 本身不能直接获取流,需要先将其转换为单列集合(KeySet 或 EntrySet)后再获取。

HashMap<String, Integer> map = new HashMap<>();

// 1. 对键获取流
Stream<String> keyStream = map.keySet().stream();

// 2. 对键值对获取流 (常用)
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

3. 数组

使用 Arrays 工具类的静态方法。

int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr); 

4. 零散数据

使用 Stream.of() 静态方法。

注意:如果传入的是基本数据类型的数组(如 int[]),Stream.of 会把整个数组当成一个元素(即传递的是地址);引用类型数组则会正常展开。

Stream<Integer> stream = Stream.of(1, 2, 3, 4);

三、 第二步:Stream 中间方法 (Intermediate Operations)

中间方法具有惰性求值的特点:它们不会立即执行,只有当遇到“终结方法”时,整个流水线才会启动。

核心规则

Stream 流只能使用一次,复用会报错,建议使用链式编程

中间操作不会修改原集合的数据。

1. 筛选与切片

  • filter(Predicate):过滤。返回 false 代表当前数据舍弃,返回 true 代表保留。

    // 留下 list 中以“张”开头的数据
    list.stream().filter(ele -> ele.startsWith("张"));
    
  • limit(n):截断流,只取前 n 个数据。

  • skip(n):跳过流,扔掉前 n 个数据。

  • distinct():元素去重。

    注意:该方法依赖元素的 hashCode() 和 equals() 方法。如果是自定义对象,请务必重写这两个方法。

2. 组合 (Concat)

  • Stream.concat(a, b):将两个流合并为一个流。

3. 映射 (Map & FlatMap)

  • map(Function):转换流中的数据类型。

    // 示例:将 "张三-20" 这种字符串转为 int 类型的年龄
    List<String> list = Arrays.asList("张三-20", "李四-18");
    
    list.stream()
        .map(s -> Integer.parseInt(s.split("-")[1])) // 字符串转数字
        .forEach(System.out::println);
    
  • flatMap(Function):扁平化映射。主要作用是将一个流中的每个元素映射成一个新的流,然后将这些新的流合并成一个单一的流。

    // 示例:将嵌套列表 [[1,2], [3,4], [5,6]] 展平为 [1,2,3,4,5,6]
    List<List<Integer>> listOfLists = Arrays.asList(
        Arrays.asList(1, 2),
        Arrays.asList(3, 4),
        Arrays.asList(5, 6)
    );
    
    List<Integer> flattenedList = listOfLists.stream()
        .flatMap(List::stream) // 将每个小 List 变成 Stream
        .collect(Collectors.toList());
    

4. 排序 (Sorted)

  • sorted():根据自然顺序排序(要求元素实现 Comparable 接口)。

  • sorted(Comparator):通过自定义比较器排序。

    升序示例

    // 先按年龄排序,年龄相同则按名字排序
    List<Person> sortedList = people.stream()
        .sorted(Comparator.comparing(Person::getAge)
                          .thenComparing(Person::getName))
        .collect(Collectors.toList());
    

    降序示例: 可以在 comparing 后面链式调用 .reversed(),或者直接使用 Comparator.reverseOrder()

    // 按年龄降序
    people.stream().sorted(Comparator.comparing(Person::getAge).reversed());
    

5. 调试 (Peek)

  • peek(Consumer):类似于 forEach,但它是一个中间操作。通常用于在流的各个阶段打印日志,观察数据流向,而不打断流的处理。

四、 第三步:Stream 终结方法 (Terminal Operations)

终结方法是流操作的最后一步,调用后流即关闭,无法再次使用。

1. 遍历与统计

  • forEach(Consumer):遍历操作,返回值为 void。

    stream.forEach(ele -> System.out.println(ele));
    
  • count():返回流中数据的个数(long 类型)。

2. 查找与匹配

  • anyMatch(Predicate):判断流中是否包含任意一个满足条件的元素。

  • allMatch(Predicate):判断流中是否所有元素都满足条件。

  • findFirst():返回流中的第一个元素(返回类型为 Optional)。

3. 数组转换

  • toArray()

    • 不传参:返回 Object[]

    • 传参:返回指定类型的数组。

    // lambda 表达式中的 value 代表数组的长度
    String[] arr = stream.toArray(value -> new String[value]);
    // 或者使用方法引用
    String[] arr2 = stream.toArray(String[]::new);
    

4. 归约 (Reduce)

  • reduce():用于将流中的所有元素结合起来,得到一个值。例如求和、求最大值等。

    // 计算 1-10 的和
    Integer sum = Stream.iterate(1, x -> x + 1).limit(10)
                        .reduce(0, (a, b) -> a + b);
    

5. 收集 (Collect)

这是最常用的终结方法,将流转变为集合或其他数据结构。

  • Collectors.toList() / toSet():收集到 List 或 Set 中。

  • Collectors.toMap():收集到 Map 中。

    注意:toMap 如果遇到重复的 Key 会报错,建议处理 Key 冲突。

    示例:将 "名字-性别-年龄" 格式的数据转为 Map,以名字为 Key,年龄为 Value。

    List<String> list = Arrays.asList("张三-男-20", "李四-女-18");
    
    Map<String, Integer> map = list.stream()
        .collect(Collectors.toMap(
            s -> s.split("-")[0],                // Key: 名字
            s -> Integer.parseInt(s.split("-")[2]) // Value: 年龄
        ));
    
  • Collectors.groupingBy()分组(高频用法)。 根据某个分类函数对流中的元素进行分组,返回一个 Map。

    场景 1:基础分组

    // 按照年龄分组,返回 Map<Integer, List<Person>>
    Map<Integer, List<Person>> peopleByAge = people.stream()
        .collect(Collectors.groupingBy(Person::getAge));
    

    场景 2:分组并统计

    // 按照年龄分组,并计算每个年龄段的人数,返回 Map<Integer, Long>
    Map<Integer, Long> peopleCountByAge = people.stream()
        .collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
    

五、 进阶:方法引用 (Method References)

当 Lambda 表达式体中仅仅是调用一个已存在的方法时,可以使用方法引用 :: 来简化代码,使代码更具可读性。

1. 静态方法引用

格式:ClassName::staticMethod

// Lambda: s -> Integer.parseInt(s)
// 引用:   Integer::parseInt
list.stream().map(Integer::parseInt).collect(Collectors.toList());

2. 实例方法引用

格式:object::instanceMethod (对象名::成员方法)

StringBuilder sb = new StringBuilder();
// Lambda: s -> sb.append(s)
// 引用:   sb::append
list.stream().forEach(sb::append);

3. 特定类型的任意对象的实例方法引用

格式:ClassName::method 适用场景:Lambda 的第一个参数是方法的调用者,后面的参数是方法的形参。

List<String> words = Arrays.asList("apple", "banana");

// Lambda: (s) -> s.length()  --> 相当于调用 "apple".length()
// 引用:   String::length
List<Integer> lengths = words.stream()
    .map(String::length)
    .collect(Collectors.toList());

4. 构造方法引用

格式:ClassName::new

List<String> names = Arrays.asList("Alice", "Bob");

// Lambda: name -> new Person(name)
// 引用:   Person::new
List<Person> people = names.stream()
    .map(Person::new)
    .collect(Collectors.toList());

六、 总结

Java Stream API 的核心流程可以概括为: 数据源 (Source) -> 中间操作 (Intermediate) -> 终结操作 (Terminal)

到此这篇关于Java Stream流常用方法实战指南的文章就介绍到这了,更多相关Java Stream流常用方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring中的@CrossOrigin注解的使用详细解读

    Spring中的@CrossOrigin注解的使用详细解读

    这篇文章主要介绍了Spring中的@CrossOrigin注解的使用详细解读,跨源资源共享(CORS),是由大多数浏览器实现的W3C规范,允许对跨域请求进行灵活授权,用来代替IFRAME或JSONP等非正规实现方式,需要的朋友可以参考下
    2023-11-11
  • java九九乘法表示例

    java九九乘法表示例

    这篇文章主要介绍了java九九乘法表示例,需要的朋友可以参考下
    2014-04-04
  • SpringBoot实现调用自定义的应用程序((最新推荐)

    SpringBoot实现调用自定义的应用程序((最新推荐)

    这篇文章主要介绍了SpringBoot实现调用自定义的应用程序的相关知识,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-06-06
  • Java SpringBoot核心源码详解

    Java SpringBoot核心源码详解

    这篇文章主要为大家介绍了Java SpringBoot核心源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-12-12
  • Java实现精准Excel数据排序的方法详解

    Java实现精准Excel数据排序的方法详解

    在数据处理或者数据分析的场景中,需要对已有的数据进行排序,在Excel中可以通过排序功能进行整理数据,而在Java中,则可以借助Excel表格插件对数据进行批量排序,下面我们就来学习一下常见的数据排序方法吧
    2023-10-10
  • Java中如何实现类的热加载和热部署详解

    Java中如何实现类的热加载和热部署详解

    在应用运行的时升级软件,无需重新启动的方式有两种,热部署和热加载,这篇文章主要介绍了Java中如何实现类的热加载和热部署的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-05-05
  • 实现分布式WebSocket集群的方法

    实现分布式WebSocket集群的方法

    本文总结出了几个实现分布式WebSocket集群的办法,从zuul到spring cloud gateway的不同尝试,总结出了这篇文章,希望能帮助到某些人,并且能一起分享这方面的想法与研究
    2022-03-03
  • 如何测试Spring MVC应用

    如何测试Spring MVC应用

    这篇文章主要介绍了如何测试Spring MVC应用,帮助大家更好的理解和使用spring框架,感兴趣的朋友可以了解下
    2020-10-10
  • Spring Cloud工程搭建过程详解

    Spring Cloud工程搭建过程详解

    文章介绍了如何使用父子工程搭建SpringCloud项目,包括创建父工程和子项目,以及管理依赖版本,感兴趣的朋友一起看看吧
    2025-02-02
  • Spring Boot 和 Spring 到底有啥区别你知道吗

    Spring Boot 和 Spring 到底有啥区别你知道吗

    Spring Boot框架的核心就是自动配置,只要存在相应的jar包,Spring就帮我们自动配置。接下来通过本文给大家介绍Spring与Spring boot的区别介绍,非常不错,需要的朋友参考下吧
    2021-08-08

最新评论