Java Stream流全解析:从入门到实战,简化集合操作

0 阅读20分钟

Java Stream流全解析:从入门到实战,简化集合操作

在Java开发中,集合操作是高频场景——遍历、过滤、排序、映射、统计等操作几乎贯穿所有业务代码。在Java 8之前,我们只能通过for循环、迭代器(Iterator)来处理集合,代码繁琐、可读性差,还容易出现冗余重复的逻辑。

Java 8引入的Stream流(java.util.stream.Stream),彻底改变了集合操作的方式。它以“声明式编程”为核心,将集合操作抽象为一系列流式操作,代码更简洁、优雅,同时支持并行处理,能大幅提升数据处理效率。

本文将从Stream流的本质、核心特性,到创建方式、中间操作、终止操作,再到实战案例、并行流、避坑指南,层层递进,用通俗的语言+可直接复用的代码,帮你彻底吃透Stream流,告别繁琐的for循环,提升开发效率。

一、先搞懂:什么是Java Stream流?

Stream流不是集合,也不是数据结构,它是一种数据处理的管道——本质上是一个“来自数据源的元素序列,支持一系列中间操作和一个终止操作”。

简单来说,Stream流的核心逻辑是:从数据源(如集合、数组)中获取元素,通过一系列“中间操作”对元素进行处理(过滤、排序、映射等),最后通过“终止操作”得到处理结果。整个过程就像水流经过管道,每一段管道对水流进行一次处理,最终流出的是处理后的结果。

1.1 Stream流的核心特性(必记)

  • 不存储数据:Stream流本身不存储元素,元素来源于数据源(集合、数组等),处理完成后也不会修改原数据源;

  • 声明式编程:只需关注“要做什么”(如过滤出年龄大于18的用户),无需关注“怎么做”(无需写for循环);

  • 链式操作:中间操作可以链式串联,形成一个处理管道,代码简洁优雅;

  • 惰性求值:中间操作不会立即执行,只有当执行终止操作时,所有中间操作才会一次性执行(提升效率);

  • 可并行:支持并行处理,自动利用多核CPU,无需手动编写多线程代码;

  • 一次性使用:一个Stream流只能执行一次终止操作,执行完毕后,流就会关闭,无法重复使用。

1.2 Stream流 vs 集合(直观区别)

很多新手会把Stream流和集合混淆,用表格清晰区分二者的核心差异:

对比维度集合(Collection)Stream流
本质数据结构,用于存储元素数据处理管道,用于处理元素
操作方式命令式编程(for循环、迭代器,关注“怎么做”)声明式编程(关注“做什么”)
执行时机操作立即执行惰性求值,终止操作时才执行
可复用性可重复操作(多次遍历、修改)一次性使用,终止后关闭
核心用途存储数据处理数据(过滤、排序、映射等)

二、Stream流的使用步骤(固定流程)

使用Stream流的流程固定为3步,缺一不可,记住这个流程,就能快速上手Stream流:

  1. 创建Stream流:从数据源(集合、数组、值等)中创建Stream流;

  2. 中间操作(中间方法):对Stream流中的元素进行处理(过滤、排序、映射等),可以多个中间操作链式串联;

  3. 终止操作(终止方法):触发Stream流的执行,得到处理结果(如集合、数值、布尔值等),执行后流关闭。

核心记忆:创建流 → 处理流 → 终止流;中间操作“链式串联”,终止操作“触发执行”。

三、第一步:创建Stream流(4种常用方式)

Stream流的创建方式有多种,最常用的是从集合、数组创建,还有从单个值、生成器创建,以下是4种高频创建方式,代码可直接复用。

3.1 从集合创建(最常用)

Java 8为Collection接口新增了两个默认方法,用于创建Stream流:

  • stream():创建串行流(单线程处理);

  • parallelStream():创建并行流(多线程处理)。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreateDemo {
    public static void main(String[] args) {
        // 1. 从List集合创建Stream流
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Stream");
        list.add("Demo");
        
        // 串行流(单线程)
        Stream<String> stream = list.stream();
        // 并行流(多线程)
        Stream<String> parallelStream = list.parallelStream();
    }
}

3.2 从数组创建

通过java.util.Arrays.stream()方法,从数组创建Stream流,支持基本类型数组和引用类型数组。

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamCreateDemo2 {
    public static void main(String[] args) {
        // 1. 引用类型数组(String数组)
        String[] strArray = {"Java", "Stream", "Demo"};
        Stream<String> strStream = Arrays.stream(strArray);
        
        // 2. 基本类型数组(int数组)
        int[] intArray = {1, 2, 3, 4, 5};
        IntStream intStream = Arrays.stream(intArray); // 基本类型流(IntStream、LongStream、DoubleStream)
        
        // 3. 截取数组的一部分创建流(从索引1开始,到索引3结束,不包含3)
        Stream<String> partStream = Arrays.stream(strArray, 1, 3);
    }
}

补充:基本类型流(IntStream、LongStream、DoubleStream)是Stream流的子类,专门用于处理基本类型数据,避免自动装箱/拆箱的性能损耗。

3.3 从单个值/多个值创建

通过Stream.of()方法,从单个值或多个值直接创建Stream流,参数可以是任意类型、任意数量。

import java.util.stream.Stream;

public class StreamCreateDemo3 {
    public static void main(String[] args) {
        // 1. 多个值创建流
        Stream<String> stream1 = Stream.of("Java", "Stream", "Demo");
        
        // 2. 单个值创建流
        Stream<String> stream2 = Stream.of("Hello Stream");
        
        // 3. 基本类型值(会自动装箱为包装类)
        Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);
    }
}

3.4 从生成器创建(无限流)

通过Stream.generate()Stream.iterate()方法,创建“无限流”(元素无限多),通常需要配合limit()方法限制元素数量,否则会无限生成。

import java.util.stream.Stream;

public class StreamCreateDemo4 {
    public static void main(String[] args) {
        // 1. Stream.generate():生成无限流(参数是Supplier接口,无参返回一个值)
        // 生成10个随机整数(限制数量,否则无限生成)
        Stream<Integer> randomStream = Stream.generate(() -> (int) (Math.random() * 100)).limit(10);
        
        // 2. Stream.iterate():生成无限流(参数:初始值,UnaryOperator接口,接收一个值返回一个值)
        // 生成1-10的整数(从1开始,每次+1,限制10个)
        Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 1).limit(10);
    }
}

四、第二步:中间操作(核心,链式处理)

中间操作是Stream流的核心,用于对元素进行过滤、排序、映射、去重等处理。中间操作具有“惰性求值”特性——不会立即执行,只有当终止操作执行时,所有中间操作才会一次性执行。

中间操作分为两类:

  • 无状态操作:每个元素的处理不依赖于其他元素(如过滤、映射);

  • 有状态操作:每个元素的处理依赖于其他元素(如排序、去重、限制数量)。

以下是开发中最常用的中间操作,结合代码示例讲解,直接复用即可。

4.1 过滤(filter):筛选符合条件的元素

filter()方法接收一个Predicate接口(函数式接口,接收一个参数,返回boolean),筛选出返回值为true的元素。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamFilterDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        
        // 过滤:筛选出偶数(中间操作,未执行)
        Stream<Integer> evenStream = list.stream().filter(n -> n % 2 == 0);
        
        // 终止操作:打印筛选结果(触发中间操作执行)
        evenStream.forEach(System.out::println); // 输出:2 4 6
    }
}

4.2 映射(map):将元素转换为另一种类型

map()方法接收一个Function接口(函数式接口,接收一个参数,返回一个值),将Stream流中的每个元素转换为另一种类型,生成一个新的Stream流。

import java.util.ArrayList;
import java.util.List;

public class StreamMapDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Stream");
        list.add("Demo");
        
        // 映射:将字符串转换为其长度(String → Integer)
        list.stream()
            .map(String::length) // 等价于 s -> s.length()
            .forEach(System.out::println); // 输出:4 6 4
    }
}

补充:如果是基本类型流(如IntStream),可使用mapToInt()mapToLong()等方法,避免自动装箱/拆箱。

4.3 排序(sorted):对元素进行排序

sorted()方法有两个重载:

  • sorted():无参,默认按元素的自然顺序排序(需元素实现Comparable接口);

  • sorted(Comparator comparator):有参,按自定义比较器排序(灵活,最常用)。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class StreamSortedDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(1);
        list.add(4);
        list.add(2);
        list.add(5);
        
        // 1. 自然排序(默认升序)
        System.out.println("自然排序(升序):");
        list.stream()
            .sorted()
            .forEach(System.out::println); // 输出:1 2 3 4 5
        
        // 2. 自定义排序(降序)
        System.out.println("自定义排序(降序):");
        list.stream()
            .sorted(Comparator.reverseOrder()) // 降序比较器
            .forEach(System.out::println); // 输出:5 4 3 2 1
        
        // 示例:对字符串按长度降序排序
        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Stream");
        strList.add("Demo");
        System.out.println("字符串按长度降序:");
        strList.stream()
            .sorted((s1, s2) -> s2.length() - s1.length()) // 自定义比较器
            .forEach(System.out::println); // 输出:Stream Java Demo
    }
}

4.4 去重(distinct):去除重复元素

distinct()方法根据元素的equals()方法去重,无需额外参数,简单实用。

import java.util.ArrayList;
import java.util.List;

public class StreamDistinctDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(3);
        list.add(3);
        
        // 去重:去除重复元素
        list.stream()
            .distinct()
            .forEach(System.out::println); // 输出:1 2 3
    }
}

4.5 限制数量(limit):截取前N个元素

limit(long n)方法截取Stream流的前n个元素,若元素数量小于n,则返回所有元素;常用于无限流的限制。

import java.util.stream.Stream;

public class StreamLimitDemo {
    public static void main(String[] args) {
        // 生成100个随机整数,只取前5个
        Stream.generate(() -> (int) (Math.random() * 100))
              .limit(5)
              .forEach(System.out::println);
    }
}

4.6 跳过元素(skip):跳过前N个元素

skip(long n)方法跳过Stream流的前n个元素,若元素数量小于n,则返回空流;常与limit()配合使用,实现分页。

import java.util.ArrayList;
import java.util.List;

public class StreamSkipDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        
        // 跳过前5个元素,取后面3个(模拟分页:第2页,每页3条)
        list.stream()
            .skip(5) // 跳过前5个(1-5)
            .limit(3) // 取3个(6-8)
            .forEach(System.out::println); // 输出:6 7 8
    }
}

4.7 扁平化(flatMap):将嵌套流转换为单个流

当Stream流中的元素是“流”(如List<List>)时,使用flatMap()方法将嵌套的流“扁平化”,转换为单个Stream流,避免嵌套遍历。

import java.util.ArrayList;
import java.util.List;

public class StreamFlatMapDemo {
    public static void main(String[] args) {
        // 嵌套集合:List<List<String>>
        List<List<String>> nestedList = new ArrayList<>();
        List<String> list1 = new ArrayList<>();
        list1.add("Java");
        list1.add("Python");
        List<String> list2 = new ArrayList<>();
        list2.add("Stream");
        list2.add("Lambda");
        nestedList.add(list1);
        nestedList.add(list2);
        
        // 扁平化:将List<List<String>> 转换为 Stream<String>
        nestedList.stream()
                 .flatMap(List::stream) // 等价于 list -> list.stream()
                 .forEach(System.out::println); // 输出:Java Python Stream Lambda
    }
}

五、第三步:终止操作(触发执行,获取结果)

终止操作是Stream流的“触发点”,只有执行终止操作,之前的所有中间操作才会一次性执行。终止操作执行后,Stream流会关闭,无法重复使用。

终止操作分为两类:

  • 非短路操作:必须处理完所有元素,才能得到结果(如forEach、collect);

  • 短路操作:只要找到符合条件的元素,就立即终止处理,无需处理所有元素(如findFirst、anyMatch)。

以下是开发中最常用的终止操作,结合代码示例讲解。

5.1 遍历(forEach):最常用的终止操作

forEach()方法接收一个Consumer接口(函数式接口,接收一个参数,无返回值),遍历Stream流中的每个元素,执行指定操作(如打印、修改)。

import java.util.ArrayList;
import java.util.List;

public class StreamForEachDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Stream");
        list.add("Demo");
        
        // 遍历打印每个元素
        list.stream().forEach(System.out::println);
        
        // 遍历,对每个元素进行处理(如转大写)
        list.stream().forEach(s -> System.out.println(s.toUpperCase())); // 输出:JAVA STREAM DEMO
    }
}

5.2 收集(collect):将流转换为集合(最常用)

collect()方法是最常用的终止操作,用于将Stream流转换为集合(List、Set、Map等),需要配合Collectors工具类使用。

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class StreamCollectDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(2);
        list.add(3);
        list.add(4);
        
        // 1. 转换为List集合(去重)
        List<Integer> listResult = list.stream()
                                      .distinct()
                                      .collect(Collectors.toList());
        System.out.println("转换为List:" + listResult); // 输出:[1, 2, 3, 4]
        
        // 2. 转换为Set集合(自动去重)
        Set<Integer> setResult = list.stream()
                                    .collect(Collectors.toSet());
        System.out.println("转换为Set:" + setResult); // 输出:[1, 2, 3, 4]
        
        // 3. 转换为ArrayList(指定集合类型)
        ArrayList<Integer> arrayListResult = list.stream()
                                                .distinct()
                                                .collect(Collectors.toCollection(ArrayList::new));
    }
}

5.3 统计(count、max、min、average):获取数值统计信息

针对数值类型的Stream流(如IntStream、Stream),可使用统计相关的终止操作,获取元素的数量、最大值、最小值、平均值等。

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamStatisticDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        
        // 1. 统计元素数量(count)
        long count = list.stream().count();
        System.out.println("元素数量:" + count); // 输出:5
        
        // 2. 求最大值(max)
        Optional<Integer> max = list.stream().max(Integer::compareTo);
        System.out.println("最大值:" + max.get()); // 输出:5
        
        // 3. 求最小值(min)
        Optional<Integer> min = list.stream().min(Integer::compareTo);
        System.out.println("最小值:" + min.get()); // 输出:1
        
        // 4. 求平均值(average,需转换为基本类型流)
        double average = list.stream()
                            .mapToInt(Integer::intValue)
                            .average()
                            .getAsDouble();
        System.out.println("平均值:" + average); // 输出:3.0
    }
}

补充:Optional是Java 8引入的容器类,用于避免空指针异常,后续会简单讲解。

5.4 匹配(anyMatch、allMatch、noneMatch):判断元素是否符合条件

匹配类终止操作返回boolean值,用于判断Stream流中的元素是否符合指定条件,属于短路操作(找到符合条件的元素后立即终止)。

  • anyMatch():是否存在至少一个元素符合条件;

  • allMatch():是否所有元素都符合条件;

  • noneMatch():是否所有元素都不符合条件。

import java.util.ArrayList;
import java.util.List;

public class StreamMatchDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        
        // 1. anyMatch:是否存在偶数(存在,返回true)
        boolean hasEven = list.stream().anyMatch(n -> n % 2 == 0);
        System.out.println("存在偶数:" + hasEven); // 输出:true
        
        // 2. allMatch:是否所有元素都是正数(是,返回true)
        boolean allPositive = list.stream().allMatch(n -> n > 0);
        System.out.println("所有元素都是正数:" + allPositive); // 输出:true
        
        // 3. noneMatch:是否所有元素都不是负数(是,返回true)
        boolean noNegative = list.stream().noneMatch(n -> n < 0);
        System.out.println("所有元素都不是负数:" + noNegative); // 输出:true
    }
}

5.5 查找(findFirst、findAny):获取流中的元素

查找类终止操作返回Optional类型,用于获取Stream流中的元素,属于短路操作。

  • findFirst():获取流中的第一个元素;

  • findAny():获取流中的任意一个元素(串行流中通常是第一个,并行流中是任意一个)。

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamFindDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Stream");
        list.add("Demo");
        
        // 1. findFirst:获取第一个元素
        Optional<String> first = list.stream().findFirst();
        System.out.println("第一个元素:" + first.get()); // 输出:Java
        
        // 2. findAny:获取任意一个元素(串行流中通常是第一个)
        Optional<String> any = list.stream().findAny();
        System.out.println("任意一个元素:" + any.get()); // 输出:Java
        
        // 并行流中findAny()可能返回任意元素
        Optional<String> parallelAny = list.parallelStream().findAny();
        System.out.println("并行流中任意元素:" + parallelAny.get());
    }
}

5.6 归约(reduce):将元素聚合为一个值

reduce()方法将Stream流中的所有元素聚合为一个值(如求和、求乘积),接收一个累加器(BinaryOperator接口),可选指定初始值。

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamReduceDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        
        // 1. 无初始值:返回Optional类型(避免空流导致的空指针)
        Optional<Integer> sum1 = list.stream().reduce((a, b) -> a + b); // 求和
        System.out.println("求和(无初始值):" + sum1.get()); // 输出:15
        
        // 2. 有初始值:返回具体类型(无需Optional)
        int sum2 = list.stream().reduce(0, (a, b) -> a + b); // 初始值0,求和
        System.out.println("求和(有初始值):" + sum2); // 输出:15
        
        // 3. 求乘积
        int product = list.stream().reduce(1, (a, b) -> a * b);
        System.out.println("乘积:" + product); // 输出:120
    }
}

六、实战案例:Stream流综合应用(开发高频)

结合实际开发场景,整理3个高频实战案例,涵盖Stream流的创建、中间操作、终止操作的综合使用,代码可直接复用。

案例1:用户列表筛选与排序

场景:有一个用户列表,筛选出年龄大于18、性别为男的用户,按年龄降序排序,最后收集到List集合中。

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

// 用户实体类
class User {
    private String name;
    private int age;
    private String gender;

    // 构造方法、getter、toString
    public User(String name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public String getGender() { return gender; }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + ", gender='" + gender + "'}";
    }
}

public class StreamUserDemo {
    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("张三", 20, "男"));
        userList.add(new User("李四", 17, "男"));
        userList.add(new User("王五", 25, "男"));
        userList.add(new User("赵六", 22, "女"));
        userList.add(new User("钱七", 19, "男"));
        
        // 筛选+排序+收集
        List<User> resultList = userList.stream()
                                        .filter(user -> user.getAge() > 18) // 筛选年龄>18
                                        .filter(user -> "男".equals(user.getGender())) // 筛选性别男
                                        .sorted(Comparator.comparingInt(User::getAge).reversed()) // 按年龄降序
                                        .collect(Collectors.toList()); // 收集到List
        
        // 遍历结果
        resultList.forEach(System.out::println);
        // 输出:
        // User{name='王五', age=25, gender='男'}
        // User{name='赵六'...}(被过滤,不输出)
        // User{name='钱七', age=19, gender='男'}
        // User{name='张三', age=20, gender='男'}
    }
}

案例2:字符串列表处理(去重、过滤、拼接)

场景:有一个字符串列表,去除重复元素,过滤掉长度小于3的字符串,将剩余字符串按逗号拼接成一个字符串。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamStringDemo {
    public static void main(String[] args) {
        List<String> strList = new ArrayList<>();
        strList.add("Java");
        strList.add("Java");
        strList.add("Stream");
        strList.add("Demo");
        strList.add("a");
        strList.add("ab");
        
        // 去重+过滤+拼接
        String result = strList.stream()
                              .distinct() // 去重
                              .filter(s -> s.length() >= 3) // 过滤长度>=3的字符串
                              .collect(Collectors.joining(",")); // 按逗号拼接
        
        System.out.println("拼接结果:" + result); // 输出:Java,Stream,Demo
    }
}

案例3:数值列表统计(求和、平均值、最大值)

场景:有一个整数列表,统计列表中所有偶数的和、平均值、最大值。

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class StreamNumberDemo {
    public static void main(String[] args) {
        List<Integer> numList = new ArrayList<>();
        numList.add(1);
        numList.add(2);
        numList.add(3);
        numList.add(4);
        numList.add(5);
        numList.add(6);
        
        // 筛选偶数,转换为IntStream(提升性能)
        var evenStream = numList.stream()
                                .filter(n -> n % 2 == 0)
                                .mapToInt(Integer::intValue);
        
        // 统计
        int sum = evenStream.sum(); // 求和
        double average = evenStream.average().getAsDouble(); // 平均值
        int max = evenStream.max().getAsInt(); // 最大值
        
        System.out.println("偶数和:" + sum); // 输出:12(2+4+6)
        System.out.println("偶数平均值:" + average); // 输出:4.0
        System.out.println("偶数最大值:" + max); // 输出:6
    }
}

七、并行流(Parallel Stream):提升处理效率

Stream流分为串行流和并行流,并行流会自动利用多核CPU,将任务分配到多个线程中执行,适合处理大量数据,能大幅提升处理效率。

7.1 并行流的创建方式

  • 方式1:通过集合的parallelStream()方法创建;

  • 方式2:通过串行流的parallel()方法转换为并行流。

import java.util.ArrayList;
import java.util.List;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 1; i <= 1000000; i++) {
            list.add(i);
        }
        
        // 方式1:集合创建并行流
        long start1 = System.currentTimeMillis();
        list.parallelStream()
            .filter(n -> n % 2 == 0)
            .count();
        long end1 = System.currentTimeMillis();
        System.out.println("并行流处理时间:" + (end1 - start1) + "ms");
        
        // 方式2:串行流转换为并行流
        long start2 = System.currentTimeMillis();
        list.stream()
            .parallel() // 转换为并行流
            .filter(n -> n % 2 == 0)
            .count();
        long end2 = System.currentTimeMillis();
        System.out.println("串行流转并行流处理时间:" + (end2 - start2) + "ms");
        
        // 对比串行流处理时间
        long start3 = System.currentTimeMillis();
        list.stream()
            .filter(n -> n % 2 == 0)
            .count();
        long end3 = System.currentTimeMillis();
        System.out.println("串行流处理时间:" + (end3 - start3) + "ms");
    }
}

运行结果:并行流处理时间远小于串行流(数据量越大,差距越明显)。

7.2 并行流的注意事项(避坑)

  • 线程安全问题:并行流是多线程处理,若中间操作中修改共享变量(如全局变量),会出现线程安全问题,需使用线程安全的容器或同步机制;

  • 不适合小数据量:并行流的线程切换、任务分配有一定开销,小数据量场景下,并行流效率可能不如串行流;

  • 顺序不保证:并行流处理时,元素的顺序可能会被打乱(如forEach遍历的顺序),若需要保证顺序,可使用forEachOrdered()方法;

  • 避免副作用:中间操作中尽量避免修改外部状态(如修改集合、全局变量),否则会导致结果不可预期。

八、Stream流避坑指南(开发高频)

Stream流看似简单,但新手容易踩坑,导致结果异常、性能低下等问题,以下是6个高频坑点,结合实例说明。

坑点1:Stream流只能使用一次

一个Stream流执行终止操作后,会立即关闭,无法重复使用,再次使用会抛出异常。

坑点2:中间操作不立即执行(惰性求值)

新手容易误以为中间操作会立即执行,实际上,只有执行终止操作时,所有中间操作才会一次性执行。

坑点3:并行流的线程安全问题

并行流多线程处理时,修改共享变量会出现线程安全问题,导致结果错误。

坑点4:Optional使用不当(空指针)

终止操作(如findFirst、max)返回Optional类型,若直接调用get()方法,当流为空时,会抛出NoSuchElementException(空指针类似异常)。

坑点5:滥用并行流(小数据量场景)

并行流的线程切换、任务分配有开销,小数据量场景下,并行流效率不如串行流,甚至更慢。

坑点6:flatMap使用不当(嵌套流未扁平化)

当Stream流中的元素是嵌套集合时,若使用map()而非flatMap(),会得到“流嵌套流”,无法正确遍历。

九、面试高频:Stream流相关面试题(附答案)

Stream流是Java 8+的核心知识点,也是大厂面试的高频考点,以下是5道最常考的面试题,附简洁答案(面试直接用)。

面试题1:Stream流的核心特性有哪些?

答:核心特性有5点:① 不存储数据;② 声明式编程;③ 链式操作;④ 惰性求值;⑤ 可并行;⑥ 一次性使用。

面试题2:Stream流的中间操作和终止操作有什么区别?

答:① 中间操作:惰性求值,不立即执行,只构建处理管道,可链式串联;② 终止操作:触发所有中间操作执行,执行后流关闭,返回处理结果(如集合、数值)。

面试题3:Stream流和集合的区别是什么?

答:① 本质:集合是数据结构,用于存储元素;Stream流是数据处理管道,用于处理元素;② 操作方式:集合是命令式编程,Stream流是声明式编程;③ 执行时机:集合操作立即执行,Stream流惰性求值;④ 可复用性:集合可重复使用,Stream流一次性使用。

面试题4:并行流和串行流的区别是什么?什么时候用并行流?

答:区别:① 串行流:单线程处理,顺序执行;② 并行流:多线程处理,自动利用多核CPU,顺序不保证。

使用场景:数据量较大(如10万+元素),且中间操作无线程安全问题时,用并行流提升效率;小数据量场景用串行流(避免线程开销)。

面试题5:Optional类的作用是什么?常用方法有哪些?

答:作用:避免空指针异常,封装可能为null的对象,提供安全的访问方式。

常用方法:① orElse():存在则返回对象,不存在返回默认值;② orElseThrow():存在则返回对象,不存在抛出异常;③ ifPresent():存在则执行指定操作;④ get():直接获取对象(空时报错,不推荐)。

十、总结

Stream流的核心价值是“简化集合操作,提升开发效率”,它以声明式编程为核心,将繁琐的for循环、迭代器操作,简化为链式的流式操作,同时支持并行处理,能应对大量数据的处理场景。

掌握Stream流的关键的是:牢记“创建流→中间操作→终止操作”的固定流程,理解中间操作的“惰性求值”特性,熟练使用常用的中间操作(filter、map、sorted)和终止操作(forEach、collect、count),同时避开并行流线程安全、Stream流重复使用等坑点。

在实际开发中,建议多使用Stream流替代传统的for循环,既能简化代码,又能提升代码的可读性和可维护性。Stream流的学习没有捷径,多练、多实战,结合业务场景灵活运用,就能真正掌握它的用法,让它成为你开发中的“效率神器”。 Java Stream流全解析:从入门到实战,简化集合操作 在Java开发中,集合操作是高频场景——遍历、过滤、排序、映射、统计等操作几乎贯穿所有业务代码。