Java Stream流操作全解析

一、 什么是 Stream 流?

Stream 是 Java 8 引入的一个强大的抽象概念,用于处理数据集合(尤其是 Collection 类型的数据)。它不同于传统的 Collection(如 ListSet),Stream 本身不存储数据,而是对数据源(如集合、数组)进行复杂操作(如过滤、映射、排序、聚合)的管道。你可以把它想象成一条流水线,数据像水流一样通过各个处理环节。

二、Stream 的核心特点

  • 链式操作: 可以将多个操作(如 filtermapsorted)连接起来形成一个操作流水线。
  • 延迟执行: 中间操作(如 filtermap)只是记录操作步骤,并不会立即执行,直到遇到终端操作(如 collectforEach)才会触发整个流水线的执行。
  • 内部迭代: 遍历操作由 Stream API 在内部完成,开发者只需关注"做什么",而不是"怎么做"。
  • 函数式风格: 大量使用 Lambda 表达式作为参数,代码更简洁。
  • 支持并行: 可以非常容易地转换为并行流以利用多核处理器提升性能。

三、Stream 操作的分类

1.获取流


一、从集合获取

通过集合类的 stream() 方法:

java 复制代码
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 获取顺序流
Stream<String> parallelStream = list.parallelStream(); // 获取并行流

二、从数组获取

使用 Arrays.stream()

java 复制代码
int[] array = {1, 2, 3};
IntStream intStream = Arrays.stream(array); // 基本类型数组
String[] strArray = {"x", "y"};
Stream<String> strStream = Arrays.stream(strArray); // 对象数组

使用Stream类提供的方法:Stream.of(T...values)

java 复制代码
import java.util.stream.Stream;

public class StreamOfExample {
    public static void main(String[] args) {
        // 示例 1: 创建一个包含字符串的流
        Stream<String> stringStream = Stream.of("Java", "Python", "C++", "JavaScript");
        stringStream.forEach(System.out::println); // 输出: Java Python C++ JavaScript

        // 示例 2: 创建一个包含整数的流 (注意: 这里传入的是Integer对象,不是int基本类型)
        Stream<Integer> integerStream = Stream.of(10, 20, 30, 40);
        integerStream.forEach(num -> System.out.println(num * 2)); // 输出: 20 40 60 80

        // 示例 3: 创建一个包含多个元素的流 (甚至可以是不同类型的对象,只要它们有共同父类/接口)
        // 这里所有元素都是Number的子类
        Stream<Number> numberStream = Stream.of(100, 3.14, 200L);
        numberStream.forEach(System.out::println); // 输出: 100 3.14 200
    }
}
 

三、通过生成器创建

  1. Stream.generate()

    生成无限流:

    java 复制代码
    Stream<Double> randomStream = Stream.generate(Math::random).limit(5); // 5个随机数
  2. Stream.iterate()

    生成迭代流:

    java 复制代码
    Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(10); // 0,2,4,...,18

注意事项

  • 所有操作需在终端操作(如 collect(), forEach())触发前完成
  • 只能被消费一次 ,重复使用会抛出 IllegalStateException

2.中间操作(Intermediate Operations)

中间方法(intermediate operations)是流操作的核心部分,它们用于在数据流上执行各种转换和处理。这些方法返回一个新的 Stream,允许链式调用,并且是惰性求值的(lazy evaluation),这意味着它们不会立即执行,直到终端操作(如 collectforEach)被调用时才会触发实际计算。常见的中间操作有:

  • filter(Predicate<T> predicate):过滤元素,保留满足条件的元素。
  • map(Function<T, R> mapper):将元素转换成另一种形式或提取信息。
  • flatMap(Function<T, Stream<R>> mapper):将每个元素转换成一个 Stream,然后将所有 Stream 连接成一个 Stream
  • distinct():去除重复元素(基于 equals)。

如果希望自定义对象能够去重复,重写对象的hashCode和equals方法才可以

  • sorted() / sorted(Comparator<T> comparator):排序。
  • peek(Consumer<T> action):对每个元素执行操作,主要用于调试。
  • limit(long maxSize):截取前 N 个元素。
  • skip(long n):跳过前 N 个元素。

这些方法可以组合使用,形成链式操作,实现复杂的数据处理。

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamIntermediateExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 4, 6);

        // 链式调用中间方法:过滤偶数、映射为平方、去重、排序
        Stream<Integer> processedStream = numbers.stream()
            .filter(n -> n % 2 == 0) // 过滤出偶数
            .map(n -> n * n)         // 计算平方
            .distinct()               // 去除重复项
            .sorted();                // 自然顺序排序

        // 终端操作:收集结果到列表
        List<Integer> result = processedStream.toList();
        System.out.println(result); // 输出: [4, 16, 36, 64, 100]
    }
}

解释

  • filter 保留偶数(2, 4, 6, 8, 10)。
  • map 计算每个偶数的平方(4, 16, 36, 64, 100)。
  • distinct 去除重复的平方值(例如原始列表有重复的 2 和 4,但平方后 4 和 16 可能重复,但在此例中不重复)。
  • sorted 对结果排序(输出有序列表)。
  • 注意:所有中间方法都是惰性的,只有在 toList() 终端操作被调用时才会执行。

重要注意事项

  • 惰性求值 :中间方法不会立即执行计算,它们只是构建操作链。实际计算在终端操作(如 collectforEach)时发生,这提高了效率。
  • 链式调用:可以连续使用多个中间方法,但每个方法都返回新流,原始流不会被修改。
  • 无状态 vs 有状态 :大多数方法(如 filtermap)是无状态的,但 distinctsortedlimitskip 是有状态的,它们可能影响性能或顺序。
  • 并行流支持 :中间方法在并行流(parallelStream())中也能工作,但需注意线程安全。

3.终端操作(Terminal Operations)

终结方法是 Stream 流水线的最后一步,它们会触发流的实际计算 ,并产生一个结果(或副作用),同时消耗该流。一旦调用终结方法,该流就不能再被使用。常见的终端操作有:

  • forEach(Consumer<T> action):遍历每个元素。
  • toArray():将元素收集到数组中。
  • reduce(...):将元素组合起来产生一个值(如求和、求最大值)。
  • collect(Collector<T, A, R> collector):将元素收集到一个容器中(如 ListSetMap)。这是最常用、最强大的终端操作。
  • min(Comparator<T> comparator) / max(Comparator<T> comparator):找最小/最大值。
  • count():统计元素个数。
  • anyMatch(Predicate<T> predicate) / allMatch / noneMatch:检查是否存在/所有/没有元素满足条件。
  • findFirst() / findAny():找到第一个/任意一个元素。

1)遍历与消费

这些方法主要用于遍历流中的元素并执行操作,不返回流本身的值,而是产生副作用或直接结束。

  • void forEach(Consumer<? super T> action)

    • 功能 :对流中的每个元素执行给定的操作 action

    • 特点

      • 不保证顺序(尤其在并行流中)。
      • 通常用于产生副作用(如打印、修改外部状态)。
    • 示例

      java 复制代码
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      names.stream().forEach(System.out::println); // 打印每个名字
  • void forEachOrdered(Consumer<? super T> action)

    • 功能 :对流中的每个元素执行给定的操作 action,并保证按照流的遭遇顺序执行(如果流有定义顺序)。

    • 特点

      • 在并行流中,虽然元素可能由不同线程处理,但 action 的执行会严格按照流中元素的顺序进行,这可能会牺牲一些并行性能。
      • 适用于需要严格顺序的副作用操作。
    • 示例

      java 复制代码
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      names.parallelStream().forEachOrdered(System.out::println); // 保证打印顺序是 Alice, Bob, Charlie

2)收集结果

这些方法将流中的元素聚合并生成一个结果,如集合、值或其他数据结构。

  • <R, A> R collect(Collector<? super T, A, R> collector)

    • 功能 :使用 Collector 策略对元素进行可变归约操作。这是最强大、最常用的终结方法之一。

    • 特点

      • Collector 指定了如何累积元素、如何组合中间结果以及最终如何将结果转换为想要的类型。
      • 常用 Collectors 工具类提供了许多预定义的收集器(如 toList(), toSet(), toMap(), joining() 等)。
    • 示例

      java 复制代码
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      List<String> list = names.stream().collect(Collectors.toList()); // 收集到 List
      Set<String> set = names.stream().collect(Collectors.toSet()); // 收集到 Set
      String joined = names.stream().collect(Collectors.joining(", ")); // 用逗号连接成字符串
      Map<Integer, List<String>> byLength = names.stream().collect(Collectors.groupingBy(String::length)); // 按字符串长度分组
  • Object[] toArray()

    • 功能 :将流中的元素收集到一个对象数组中。

    • 示例

      java 复制代码
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      Object[] array = names.stream().toArray();
  • <A> A[] toArray(IntFunction<A[]> generator)

    • 功能 :使用提供的数组构造器(generator)创建一个类型正确的数组,并将流中的元素收集进去。

    • 示例

      java 复制代码
      List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
      String[] stringArray = names.stream().toArray(String[]::new); // 创建并填充 String 数组

3)聚合与计算

这些方法对流中的元素进行统计或计算,返回一个单一结果。

  • Optional<T> reduce(BinaryOperator<T> accumulator)

    • 功能 :使用关联的累加器函数 accumulator 对流中的元素执行归约 操作。返回一个 Optional(因为流可能为空)。

    • 原理 :相当于 ((a1 op a2) op a3) op ...

    • 示例 (求最大值):

      java 复制代码
      Optional<Integer> max = Stream.of(1, 2, 3, 4, 5)
                                    .reduce(Integer::max); // Optional[5]
  • T reduce(T identity, BinaryOperator<T> accumulator)

    • 功能 :提供一个初始值 identity,然后使用累加器 accumulator 进行归约。即使流为空,也返回 identity

    • 原理 :相当于 identity op a1 op a2 op a3 op ...

    • 示例 (求和):

      java 复制代码
      int sum = Stream.of(1, 2, 3, 4, 5)
                      .reduce(0, Integer::sum); // 15
  • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)

    • 功能 :更通用的归约形式。适用于结果类型 U 与元素类型 T 不同的情况,或在并行流中需要指定组合器 combiner 来合并中间结果。

    • 示例 (连接字符串):

      java 复制代码
      String concatenated = Stream.of("a", "b", "c")
                                  .reduce("", (s, str) -> s + str, (s1, s2) -> s1 + s2); // "abc"
  • Optional<T> min(Comparator<? super T> comparator)

    • 功能 :根据提供的比较器 comparator 返回流中的最小值Optional)。

    • 示例

      java 复制代码
      Optional<String> shortest = Stream.of("apple", "banana", "orange")
                                        .min(Comparator.comparingInt(String::length)); // Optional["apple"]
  • Optional<T> max(Comparator<? super T> comparator)

    • 功能 :根据提供的比较器 comparator 返回流中的最大值Optional)。

    • 示例

      java 复制代码
      Optional<String> longest = Stream.of("apple", "banana", "orange")
                                       .max(Comparator.comparingInt(String::length)); // Optional["banana"]
  • long count()

    • 功能 :返回流中元素的数量

    • 示例

      java 复制代码
      long count = Stream.of(1, 2, 3, 4, 5).count(); // 5

4)匹配与查找

这些方法用于检查流中的元素是否满足某些条件,或查找特定元素。

  • boolean allMatch(Predicate<? super T> predicate)

    • 功能 :检查流中的所有元素 是否都满足给定的谓词 predicate全称量词 )。这是一个短路操作(遇到不满足的元素立即返回)。

    • 示例

      java 复制代码
      boolean allEven = Stream.of(2, 4, 6, 8).allMatch(n -> n % 2 == 0); // true
      boolean allEven2 = Stream.of(2, 4, 5, 8).allMatch(n -> n % 2 == 0); // false (遇到5就停了)
  • boolean anyMatch(Predicate<? super T> predicate)

    • 功能 :检查流中是否至少有一个元素 满足给定的谓词 predicate存在量词 )。这是一个短路操作(遇到满足的元素立即返回)。

    • 示例

      java 复制代码
      boolean hasEven = Stream.of(1, 3, 5, 7).anyMatch(n -> n % 2 == 0); // false
      boolean hasEven2 = Stream.of(1, 3, 4, 5).anyMatch(n -> n % 2 == 0); // true (遇到4就停了)
  • boolean noneMatch(Predicate<? super T> predicate)

    • 功能 :检查流中是否没有任何元素 满足给定的谓词 predicate(等价于 !anyMatch(predicate))。这是一个短路操作(遇到满足的元素立即返回)。

    • 示例

      java 复制代码
      boolean noEven = Stream.of(1, 3, 5, 7).noneMatch(n -> n % 2 == 0); // true
      boolean noEven2 = Stream.of(1, 3, 4, 5).noneMatch(n -> n % 2 == 0); // false (遇到4就停了)
  • Optional<T> findFirst()

    • 功能 :返回描述流中第一个元素Optional(如果流非空)。对于有顺序的流,结果是确定的。这是一个短路操作

    • 示例

      java 复制代码
      Optional<Integer> first = Stream.of(1, 2, 3).findFirst(); // Optional[1]
  • Optional<T> findAny()

    • 功能 :返回描述流中某个元素Optional(如果流非空)。在并行流中,它可能返回任何线程最先处理完的元素,性能可能优于 findFirst()。这是一个短路操作

    • 示例

      java 复制代码
      Optional<Integer> any = Stream.of(1, 2, 3).findAny(); // 可能是 Optional[1], Optional[2], Optional[3] 中的任何一个

4.收集stream流

Java 8 引入的 Stream API 提供了一种高效处理集合数据的方式。collect() 方法是 Stream 操作中的一个关键终端操作,用于将流中的元素收集 (或汇聚)成各种形式的结果,例如集合、数组或聚合值。

1. 核心方法:collect()

collect() 方法有两个主要重载形式:

1) <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)

  • supplier : 一个函数,用于创建并返回一个新的结果容器(例如,一个新的 ArrayList)。
  • accumulator: 一个函数,用于将流中的单个元素合并到结果容器中。
  • combiner : 一个函数(用于并行流),用于将两个结果容器合并成一个(例如,将两个 ArrayList 合并成一个)。
  • 这种方式较为底层,允许你自定义收集逻辑。

2) <R, A> R collect(Collector<? super T, A, R> collector) (更常用)

  • 接受一个 Collector 接口的实现。
  • java.util.stream.Collectors 工具类提供了大量预定义的、常用的收集器 (Collector 实现)。

2. 使用 Collectors 工具类收集

Collectors 提供了多种静态工厂方法,用于创建常见的收集器:

1) 收集到 List (toList())

java 复制代码
List<String> list = stream.collect(Collectors.toList());
// 在 Java 16+ 中,可以直接使用 stream.toList()

2) 收集到 Set (toSet())

java 复制代码
Set<String> set = stream.collect(Collectors.toSet()); // 自动去重

3) 收集到指定的集合类型 (toCollection())

java 复制代码
LinkedList<String> linkedList = stream.collect(Collectors.toCollection(LinkedList::new));

4) 收集到 Map (toMap())

  • 最基本的 toMap 需要两个函数:一个生成键 (keyMapper),一个生成值 (valueMapper)。

    java 复制代码
    Map<Integer, String> map = stream.collect(Collectors.toMap(
        Person::getId, // 键: 从Person对象中提取id
        Person::getName // 值: 从Person对象中提取name
    ));
  • 处理键冲突:如果键可能重复,需要提供第三个参数 (mergeFunction) 来解决冲突。

    java 复制代码
    Map<String, Integer> map = stream.collect(Collectors.toMap(
        s -> s.substring(0, 1), // 键: 字符串的首字母
        s -> s.length(),         // 值: 字符串长度
        (oldValue, newValue) -> oldValue // 冲突时保留旧值 (或 newValue, 或相加 oldValue + newValue)
    ));
  • 指定具体的 Map 实现类:使用第四个参数 (mapSupplier)。

    java 复制代码
    Map<String, Integer> treeMap = stream.collect(Collectors.toMap(
        ...,
        ...,
        ...,
        TreeMap::new
    ));

3. 示例代码

java 复制代码
import java.util.*;
import java.util.stream.*;

public class StreamCollectionExample {
    public static void main(String[] args) {
        // 示例流
        Stream<String> stringStream = Stream.of("Apple", "Banana", "Orange", "Grape", "Kiwi");

        // 1. 收集到 List
        List<String> fruitList = stringStream.collect(Collectors.toList());
        System.out.println("List: " + fruitList); // 输出: List: [Apple, Banana, Orange, Grape, Kiwi]

        // 重新生成流 (因为流是消耗性的)
        stringStream = Stream.of("Apple", "Banana", "Orange", "Grape", "Kiwi");

        // 2. 收集到 Set (自动去重,这里无重复)
        Set<String> fruitSet = stringStream.collect(Collectors.toSet());
        System.out.println("Set: " + fruitSet); // 输出顺序可能不同

        // 重新生成流
        stringStream = Stream.of("Apple", "Banana", "Orange", "Grape", "Kiwi", "Apple");

        // 3. 收集到指定的集合类型 (LinkedList)
        LinkedList<String> fruitLinkedList = stringStream.collect(Collectors.toCollection(LinkedList::new));
        System.out.println("LinkedList: " + fruitLinkedList); // 输出: LinkedList: [Apple, Banana, Orange, Grape, Kiwi, Apple]

        // 使用 Person 对象流示例
        Stream<Person> personStream = Stream.of(
            new Person(1, "Alice", 30),
            new Person(2, "Bob", 25),
            new Person(3, "Charlie", 35)
        );

        // 4. 收集到 Map (ID -> Name)
        Map<Integer, String> idToNameMap = personStream.collect(Collectors.toMap(
            Person::getId,
            Person::getName
        ));
        System.out.println("ID -> Name Map: " + idToNameMap); // 输出: {1=Alice, 2=Bob, 3=Charlie}
    }
}

四、使用示例

假设有一个字符串列表,我们要过滤出以 "A" 开头的字符串,把它们转换成大写,并收集到一个新列表中:

java 复制代码
List<String> names = Arrays.asList("Alice", "Bob", "Anna", "David", "Alex");

List<String> result = names.stream()          // 1. 获取流 (顺序流)
        .filter(name -> name.startsWith("A")) // 2. 中间操作:过滤
        .map(String::toUpperCase)            // 3. 中间操作:映射 (转为大写)
        .collect(Collectors.toList());       // 4. 终端操作:收集到 List

System.out.println(result); // 输出: [ALICE, ANNA, ALEX]

五、并行流

.stream() 改为 .parallelStream() 即可获得并行流。处理大数据集时,并行流可能更快,但需要注意线程安全和操作是否适合并行(如某些有状态的操作)。

java 复制代码
List<String> parallelResult = names.parallelStream()
        .filter(name -> name.startsWith("A"))
        .map(String::toUpperCase)
        .collect(Collectors.toList());

六、注意事项

  • 流只能消费一次: 一个 Stream 对象只能进行一次终端操作。
  • 避免副作用:Stream 操作(尤其是并行流)中修改外部状态可能导致并发问题,应尽量使用无状态的 Lambda 表达式。
  • 空指针: 注意流中元素可能为 null,处理时要小心。
  • 性能: 对于小数据集,顺序流可能比并行流更快,因为并行有额外的开销。

七、扩展:基本类型流

为了避免自动装箱/拆箱的开销,Java 提供了专门的流:

  • IntStream (用于 intshortcharbyte)
  • LongStream (用于 long)
  • DoubleStream (用于 doublefloat) 它们有专门的方法(如 sum()average())。
相关推荐
大千AI助手2 小时前
决策树悲观错误剪枝(PEP)详解:原理、实现与应用
人工智能·算法·决策树·机器学习·剪枝·大千ai助手·悲观错误剪枝
_OP_CHEN2 小时前
从零开始的Qt开发指南:(三)信号与槽的概念与使用
开发语言·c++·qt·前端开发·qt creator·信号与槽·gui开发
xiezhr2 小时前
接口开发,咱得整得“优雅”点
java·api·代码规范
九年义务漏网鲨鱼2 小时前
【机器学习算法】面试中的ROC和AUC
算法·机器学习·面试
草莓熊Lotso2 小时前
《算法闯关指南:优选算法--位运算》--38.消失的两个数字
服务器·c++·算法·1024程序员节
bagadesu2 小时前
IDEA + Spring Boot 的三种热加载方案
java·后端
二进制person3 小时前
JavaEE初阶 --文件操作和IO
java·java-ee
@老蝴3 小时前
Java EE - 线程安全的产生及解决方法
java·开发语言·java-ee