Java的Stream入门级教程

 更新时间:2025年10月31日 11:18:45   作者:不吃肘击  
Java的Stream(流)是从Java 8 引入的一套面向集合数据的声明式处理API,它让你像写“数据流水线”一样,对一组元素做筛选、变换、聚合等操作,代码更简洁、更可读,本文给大家介绍Java的Stream入门级教程,感兴趣的朋友跟随小编一起看看吧

什么是Stream流

Java 的 Stream(流)是从 Java 8 引入的一套面向集合数据的声明式处理API。它让你像写“数据流水线”一样,对一组元素做筛选、变换、聚合等操作,代码更简洁、更可读。

核心特性

管道化source → 中间操作* → 终止操作

惰性求值:只有遇到终止操作才真正执行。

无副作用:鼓励函数式写法,避免改动外部可变状态。

可并行parallelStream() 能并行处理(适合大数据量且拆分成本低的场景)。

Stream流不是数据本身,而是数据的视图,且Stream不可重复使用,且基于函数式接口

Stream流的生命周期

Java中Stream流的生命周期分三个步骤创建 → 中间操作 → 终止操作即生成数据,处理数据到最后执行结果

创建流

源:流是基于数据源如集合,数组,文件创建的,数据源可以是集合,数组,生成器等

例子:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = numbers.stream();  // 创建流

中间操作

惰性执行:中间操作是惰性求值的,他们不会立即执行。而是返回一个新的流,只有遇到终止操作的时候,中间操作才会执行。

例子:

Stream<Integer> evenNumbers = stream.filter(n -> n % 2 == 0)  // 中间操作
                                   .map(n -> n * n);          // 另一个中间操作

终止操作

触发执行:终止操作会触发流的处理链执行,一旦执行终止操作,流就会被消耗掉,而不能再使用.终止操作是流处理的真正的启动器,他会返回一个最终的结果

int sum = evenNumbers.reduce(0, Integer::sum);  // 终止操作,执行计算

流的关闭

一旦流被消耗掉就不能再被重新使用,当要再次处理数据的时候就要创建一个新的流

在关闭流的资源上对于集合类的流就不需要显示关闭但是对于文件流,网络流等最好通过close()来关闭

Stream流的创建方式

list.stream()

从集合创建
Arrays.stream(array)从数组创建
Stream.of("A","B","C")从值创建
Files.lines(Path.get("file.txt"))从文件行创建
Stream.iterate(0,n->n+1),Stream.generate(Math::random)无限创建流

常见的中间操作

filter():过滤符合的条件stream.filter(s -> s.length() > 3)
map(Function)转换每个元素或者是提取对象的某个是属性。将原始流中的每个元素映射为另一个类型或值。例子:steam.map(String::toUpperCase)
flatMap(Function)扁平化处理,用于嵌套结构例子:stream.flatMap(List::stream)
distinct()去重(基于equals()和hashCode()方法)例子:stream.distinct()
sorted()排序(自然排序或者是自定义排序)例子:stream.sorted(Comparator.reverseOrder())
liit(n)截取前n个元素例子:stream.limit(10)

skip(n)

跳过前n个元素stream.skip(5)

:

:

:


 

常用的终端操作

forEach(Consumer)遍历每个元素stream.forEach(System.out::println)
collect(Collector)收集结果(转为 List、Set、Map 等)stream.collect(Collectors.toList())
count()返回元素个数stream.count()
anyMatch(Predicate)是否有任意一个匹配stream.anyMatch(s -> s.contains("Java"))
allMatch(Predicate)是否全部匹配stream.allMatch(s -> s.length() > 3)
noneMatch(Predicate)是否没有匹配stream.noneMatch(s -> s.isEmpty())
findFirst()获取第一个元素stream.findFirst()
reduce(BinaryOperator)聚合操作(如求和)stream.reduce((a, b) -> a + b)

注意:中间操作可以有多个,但是在一个stream流中流的创建和终止操作只能有一个

实战用例

1.将一个对象列表中大于10岁的对象的名字提取出来

首先给定一个Person类(在实际开发中不建议将属性定义为public,这里只是为了演示

public class Person {
    public String name;
    public int age;
    public String city;
    public Person(String name,int age,String city){
        this.name = name;
        this.age = age;
        this.city = city;
    }
}

代码演示:

import java.util.List;
import java.util.stream.Collectors;
public class StreamTest {
    public static void main(String []str){
        List<Person> people = List.of(
                new Person("Alice", 23, "Beijing"),
                new Person("Bob", 17, "Shanghai"),
                new Person("Carol", 30, "Beijing")
        );
        //提取大于18岁的对象的名字
        List<String> collect = people.stream()//获取源数据
                .filter(p -> p.age > 18)//过滤源数据,满足条件则true则就保留
                .map(person -> person.name)//做map映射就是将这个源数据中的每个数据转换成另外一种数据,
                // 在这里面就是将每个对象转换成他们对应的名字
                .collect(Collectors.toList());//收集处理后的数据,通过列表的方式
        System.out.println(collect);
    }
}

2.分组统计,将列表中不同城市的人的数量做统计

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
    public static void main(String []str){
        List<Person> people = List.of(
                new Person("Alice", 23, "Beijing"),
                new Person("Bob", 17, "Shanghai"),
                new Person("Carol", 30, "Beijing"),
                new Person("Dave", 25, "Shanghai"),
                new Person("Eve", 22, "Beijing")
        );
        Map<String, Long> collect = people.stream()//转为流数据
                .collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
        //这个的作用是将这个流中的数据做一个分组,key是city,value是key对应的数量Collectors.counting() 是一个下游收集器,用来统计分组后的元素数量。
        //它返回一个 Long 类型的值,表示每个分组中元素的数量。在这里,它会统计每个城市中有多少个 Person 对象。
        System.out.println(collect);
    }
}

3.多级分组-分区(在城市分组的情况下判断是否成年)

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
    public static void main(String []str){
        List<Person> people = List.of(
                new Person("Alice", 23, "Beijing"),
                new Person("Bob", 17, "Shanghai"),
                new Person("Carol", 30, "Beijing"),
                new Person("Dave", 25, "Shanghai"),
                new Person("Eve", 22, "Beijing")
        );
        Map<String, Map<Boolean, List<Person>>> collect = people.stream()
                .collect(Collectors.groupingBy(Person::getCity, Collectors.partitioningBy(p -> p.age > 18)));
                //这里面的Collectors.groupingBy是将收集的数据进行分组根据city属性,他收集出来就是一个map,但是这个map的value不是一个值
        //而是一个对象这个对象是一个分区的函数partitioningBy他会将value又分成一个map将满足条件的放一组,不满足的放一组来形成一个map
        System.out.println(collect);
    }

4.聚合reduce

在java中的stream流的reduce方法是一个游泳的规约操作,将流中的元素反复结合起来最后得到一个值

reduce求和

import java.util.Arrays;
import java.util.List;
public class StreamTest {
    public static void main(String []str){
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int sum = numbers.stream()
                .reduce(0, (a, b) -> a + b);  // 0 是初始值,(a, b) -> a + b 是累加器
        System.out.println(sum);  // 输出 15
    }
}

reduce求最大值

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
                               .reduce(Integer::max);  // 使用 Integer::max 方法引用
max.ifPresent(System.out::println);  // 输出 5

在求最大值的时候就不需要初始值了

reduce拼接字符串

List<String> words = Arrays.asList("Java", "is", "awesome");
String result = words.stream()
                     .reduce("", (a, b) -> a + " " + b);  // 初始值为空字符串
System.out.println(result);  // 输出 "Java is awesome"

5.统计年龄总数

IntSummaryStatistics stats = people.stream()
    .collect(Collectors.summarizingInt(Person::age));
// stats.getCount()/getSum()/getMin()/getMax()/getAverage()

6.自定义排序

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamTest {
    public static void main(String []str){
        List<Person> people = List.of(
                new Person("Alice", 23, "Beijing"),
                new Person("Bob", 17, "Shanghai"),
                new Person("Carol", 30, "Beijing"),
                new Person("Dave", 25, "Shanghai"),
                new Person("Eve", 22, "Beijing")
        );
        Comparator<Person> byCityThenAge = Comparator
                .comparing(Person::getCity, Comparator.nullsLast(String::compareTo))//定义比较类先是通过city排序,对于null的排到最后
                .thenComparingInt(Person::getAge);//然后通过年龄进行比较
        List<Person> sorted = people.stream()
                .sorted(byCityThenAge)//通过比较类进行排序
                .collect(Collectors.toList());
        System.out.println(sorted);
    }
}

怎么使用Comparator

Comparator 是 Java 中用于比较对象的接口,常用于排序操作。它定义了如何对对象进行排序,并且可以自定义排序的规则。可以通过 Comparator 来指定对象之间的比较逻辑,并且与 Collections.sort() 或 Stream.sorted() 等方法配合使用。

Comparator接口常用方法:

  • compare(T o1, T o2):比较两个对象 o1o2,返回一个整数,通常为:
    • 负整数:表示 o1 小于 o2
    • 零:表示 o1 等于 o2
    • 正整数:表示 o1 大于 o2
  • reversed():返回一个逆序的比较器,反转当前比较器的排序顺序。
  • thenComparing(Comparator<? super T> other):链式调用,可以在现有排序规则基础上,添加一个新的排序规则,用于次级排序(如果主排序条件相等)。
  • comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator):根据指定的键进行排序,通常配合 Function 使用,从对象中提取出需要排序的字段。

多条件排序

List<Person> people = Arrays.asList(
    new Person("John", 25),
    new Person("Alice", 30),
    new Person("Bob", 20)
);

// 按名字排序,如果名字相同再按年龄排序
people.sort(Comparator.comparing(Person::getName)
                      .thenComparing(Person::getAge));

逆序排序

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

使用 Comparator.nullsLast() 处理 null 值

List<String> strings = Arrays.asList("apple", "banana", null, "cherry", null);
strings.sort(Comparator.nullsLast(Comparator.naturalOrder())); // 排序时,null 值排在最后
strings.forEach(System.out::println);

使用Comparing排序

// 按年龄排序,使用静态方法 Comparator.comparing
people.sort(Comparator.comparing(Person::getAge)); // 按年龄升序

使用Lambda表达式排序

// 使用 Lambda 表达式简化比较逻辑
Collections.sort(people, (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge())); // 按年龄升序

Stream流的优势?

1.简洁性和可读性

  • 更简洁的代码:Stream API 使用链式操作,减少了显式的循环和条件判断,使代码更简洁、易懂。
  • 函数式编程:通过 mapfilterreduce 等函数式操作,代码变得更具声明性,表示你想要做什么,而不是如何做,从而提高了可读性。

2.避免显式的循环

  • 不再手动管理迭代器:Stream API 隐藏了迭代的细节,减少了手动编写 for 循环或 Iterator 的代码。这让代码更直观、更符合直觉。

3.并行化和性能优化

  • 并行流:Stream API 支持非常方便的并行处理,几乎无需修改代码就能让程序利用多核处理器进行并行计算,从而提高处理大量数据时的性能。
  • 并行流优势:特别适合处理大规模数据集或计算密集型任务,自动进行任务分解和并行处理,无需开发者显式管理线程池。

4.惰性计算和延迟求值

  • 惰性求值:Stream 中的许多操作(如 filter, map, flatMap 等)是惰性计算的,即只有当终止操作(如 collect, forEach, reduce)触发时,流中的操作才会执行。
  • 只有当 collect() 被调用时,filtermap 才会执行,从而避免了中间计算的浪费。

5.函数式编程思想

  • 高阶函数:Stream API 使用了函数式编程的概念,支持高阶函数(可以将函数作为参数传递),这使得代码更加灵活、可复用。

6.增强的可组合性和复用性

  • 组合多个操作:Stream API 允许你将多个操作组合成一个流水线,进行一系列转换、过滤、聚合等操作,这样的链式调用使得代码更具可组合性。

7.更好的错误处理

  • 流的每一步都可自定义:Stream API 允许你对每一步进行灵活的定制。例如,通过 try-catch 语句、peek 等方法,可以更清晰地处理流的操作过程中的异常。
  • 你可以在流的操作过程中添加日志或调试信息,帮助更好地排查问题。

8.数据转换和聚合的高级功能

  • 复杂的数据转换和聚合:Stream API 提供了非常强大的聚合功能(如 collect, reduce, groupingBy, partitioningBy, summarizingInt 等),可以帮助你轻松处理复杂的数据转换和聚合任务。

9.可维护性和扩展性

  • 代码结构清晰:使用 Stream API 可以让数据处理代码更为模块化,容易理解,尤其是在面对复杂的过滤、映射、聚合等操作时。相对于传统的多层循环,Stream API 提供了更清晰的数据流转过程,有助于后续的维护和扩展。
  • 提升代码质量:流式操作鼓励无副作用的编程(即不修改原始数据),这减少了 bug 的发生概率,并且提高了代码的可测试性。

10.内存使用优化

  • 惰性加载:Stream 中的许多操作是惰性求值的,这意味着它们不会立即执行,只有当终止操作(如 collect)被调用时,才会开始处理数据。这有助于避免在某些情况下的内存浪费,尤其是在处理大型数据集时。

总结:Stream API 的优势

相比于传统的开发方式,Java Stream API 提供了以下几大优势:

  • 简洁性和可读性:代码更简洁,避免了显式的循环。
  • 函数式编程支持:通过高阶函数提供更高的灵活性和可重用性。
  • 并行处理:轻松使用 parallelStream() 实现并行处理,提高性能。
  • 惰性计算和延迟求值:避免不必要的计算,提高性能。
  • 丰富的聚合和转换功能:提供强大的数据聚合、转换、过滤等功能。
  • 函数式思维:鼓励不修改数据的不可变式操作。

到此这篇关于Java的Stream详解的文章就介绍到这了,更多相关java stream内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java经典面试题最全汇总208道(三)

    Java经典面试题最全汇总208道(三)

    这篇文章主要介绍了Java经典面试题最全汇总208道(三),本文章内容详细,该模块分为了六个部分,本次为第三部分,需要的朋友可以参考下
    2023-01-01
  • Java中List转Map List实现的几种姿势

    Java中List转Map List实现的几种姿势

    本文主要介绍了Java中List转Map List实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • 深入理解以DEBUG方式线程的底层运行原理

    深入理解以DEBUG方式线程的底层运行原理

    说到线程的底层运行原理,想必各位也应该知道我们今天不可避免的要讲到JVM 了。其实大家明白了Java的运行时数据区域,也就明白了线程的底层原理,今天带着大家一步一步DEBUG,来看看线程到底是怎么运行的,顺便把IDEA的DEBUG方法简单讲一下
    2021-06-06
  • JWT整合Springboot的方法步骤

    JWT整合Springboot的方法步骤

    本文主要介绍了JWT整合Springboot的方法步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-11-11
  • Java基础学习之运算符相关知识总结

    Java基础学习之运算符相关知识总结

    今天带大家复习Java基础知识,文中对Java运算符相关知识作了详细总结,对正在学习java基础的小伙伴们很有帮助,需要的朋友可以参考下
    2021-05-05
  • jmeter正则表达式提取器的用法与正则详解

    jmeter正则表达式提取器的用法与正则详解

    在使用Jmeter过程中,会经常使用到正则表达式提取器提取器,下面这篇文章主要给大家介绍了关于jmeter正则表达式提取器的用法与正则的相关资料,需要的朋友可以参考下
    2022-07-07
  • 详解JAVA中priorityqueue的具体使用

    详解JAVA中priorityqueue的具体使用

    这篇文章主要介绍了详解JAVA中priorityqueue的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Java实现带GUI的气泡诗词效果

    Java实现带GUI的气泡诗词效果

    这篇文章主要为大家介绍了如何利用Java实现带GUI的气泡诗词效果,文中的示例代码讲解详细,对我们学习Java有一定帮助,感兴趣的可以了解一下
    2022-12-12
  • 在Spring Boot中加载初始化数据的实现

    在Spring Boot中加载初始化数据的实现

    这篇文章主要介绍了在Spring Boot中加载初始化数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • mybatis教程之延迟加载详解

    mybatis教程之延迟加载详解

    本篇文章主要介绍了mybatis教程之延迟加载详解。详细介绍了延迟加载的意义和用法实现,有兴趣的可以了解一下
    2017-05-05

最新评论