Stream

Java 2026-05-21 24
预计阅读时间:10 分钟

Stream操作原则上都可以用传统循环实现,本质上是从传统命令式代码改为声明式(业务意图清晰)

适合 Stream

  • 数据转换、过滤、聚合
  • 链式操作清晰表达业务逻辑
  • 需要并行处理
  • 处理无限序列
  • 代码可读性优先

不适合 Stream

  • 简单循环(3-5行代码)
  • 需要中途 break 多层嵌套
  • 性能极端敏感(微秒级)
  • 调试困难的环境
  • 有检查型异常需要处理

一、构建Stream

  • Stream.of() 方法

    使用of() 方法传入可变参数

    java import java.util.stream.Stream; public class Main { public static void main(String[] args) { Stream<String> stream = Stream.of("A", "B", "C", "D"); // forEach()方法传入符合Consumer接口的void accept(T t)的方法引用: stream.forEach(System.out::println); } }

  • 基于ArraysCollection

    CollectionListSetQueue等)对象的stream() 方法

    Arrays 的静态stream()方法,为此需要传入对应数组

    java import ... public class Main { public static void main(String[] args) { Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" }); Stream<String> stream2 = List.of("X", "Y", "Z").stream(); stream1.forEach(System.out::println); stream2.forEach(System.out::println); } }

    把现有的序列变为Stream,它的元素固定

  • 基于Supplier

    Stream的静态generate()方法,传入Supplier对象

    java Stream<String> s = Stream.generate(Supplier<String> sp);

    这种Stream保存的不是元素,而是算法,因此,可表示无限序列(如果要迭代操作必须limit()

    ```java import ... public class Main { public static void main(String[] args) { Stream natual = Stream.generate(new NatualSupplier()); // 注意:无限序列必须先变成有限序列再打印: natual.limit(20).forEach(System.out::println); } }

    class NatualSupplier implements Supplier { int n = 0; public Integer get() { n++; return n; } } ```

    用时间来换空间,每次需要用到再算(惰性求值)(懒加载

  • 某些API提供接口

    1. Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容,现代推荐

      java try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) { ... }

    2. 正则表达式Pattern对象的splitAsStream()方法,把一个长字符串分割成Stream序列而非数组

      java Pattern p = Pattern.compile("\\s+"); Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog"); s.forEach(System.out::println);

  • 基本类型Stream

    使用Stream<Integer>时会频繁装箱、拆箱操作。为此,Java标准库提供了IntStreamLongStreamDoubleStream这三种使用基本类型的Stream 使用方法和泛型Stream没有大的区别

    ```java // 将int[]数组变为IntStream: IntStream is1 = Arrays.stream(new int[] { 1, 2, 3 }); // 将Stream转换为LongStream: LongStream ls2 = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);

    IntStream is3 = IntStream.range(1,100); IntStream is4 = IntStream.generate(() -> (int)(Math.random() * 100)) ```

  • 迭代和生成

    iterate() generate()
    区别 依赖前一个值 无状态,每次独立
    参数 (seed, UnaryOperator) (Supplier)
    典型用途 数列、累加、递推 随机数、常量、独立值
    是否可预测 可预测(公式驱动) 通常不可预测
    能否无限 可无限 可无限
    - 有状态迭代iterate()
    基本语法
    
    ```java
    // Java 8+
    Stream.iterate(seed, unaryOperator)
    
    // Java 9+ 带谓词版本
    Stream.iterate(seed, hasNext, next)
    ```
    
    常用
    
    ```java
    // 偶数序列
    IntStream.iterate(0, n -> n + 2)
             .limit(10)
             .forEach(System.out::println);  // 0,2,4,6,8,10,12,14,16,18
    // 等比数列(翻倍)
    Stream.iterate(1, n -> n * 2)
          .limit(10)
          .forEach(System.out::println);  // 1,2,4,8,16,32,64,128,256,512
    ```
    
    带条件
    
    ```java
    // 生成直到超过 100
    Stream.iterate(1, n -> n < 100, n -> n * 2)
          .forEach(System.out::println);  // 1,2,4,8,16,32,64
    // 等价于 for 循环
    for (int i = 1; i < 100; i *= 2) {
        System.out.println(i);
    }
    ```
    
    • 无状态生成generate()

      java Stream.generate(supplier)

      常用

      java // 每次独立调用 supplier,无状态传递 Stream.generate(Math::random) .limit(5) .forEach(System.out::println); // 0.123, 0.456, 0.789...(随机)

    使用 iterate

    java // 1. 序列有递推关系 Stream.iterate(1, n -> n * 2) // 等比数列 // 2. 需要累加/累积 Stream.iterate(0, acc -> acc + 5) // 步进累加 // 3. 数学数列 Stream.iterate(new int[]{0,1}, f -> new int[]{f[1], f[0]+f[1]}) // 斐波那契 // 4. 任何依赖前一个值的场景 Stream.iterate(LocalDate.now(), d -> d.plusDays(1)) // 日期序列

    使用 generate 

    java // 1. 随机数 Stream.generate(Math::random) // 2. 常量值 Stream.generate(() -> "constant") // 3. 独立计算(不依赖之前结果) Stream.generate(() -> new Object()) // 4. 从外部迭代器获取 Stream.generate(iterator::next) // 5. 测试数据生成 Stream.generate(() -> new User("test" + counter.getAndIncrement()))

二、使用map

Stream.map()静态方法把一个Stream转换为另一个Stream

原理是把一个Stream的每个元素通过函数接口规则映射到另一个Stream

正如其源码,使用泛型把T类型映射成R

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

// Function定义是
@FunctionalInterface
public interface Function<T, R> {
    // 将T类型转换为R:
    R apply(T t);
}

利用map() ,极大地简化了代码

import ...
public class Main {
    public static void main(String[] args) {
        List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
                .stream()      // 构造流
                .map(String::trim) // 去空格
                .map(String::toLowerCase) // 变小写
                .forEach(System.out::println); // 打印
    }
}

三、使用filter

Stream.filter()Stream对象的另一个常用转换方法。

原理是对一个Stream的每个元素进行判断,留下满足条件的构成新Stream 对象

import java.util.stream.IntStream;
public class Main {
    public static void main(String[] args) {
        IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .filter(n -> n % 2 != 0)   // 留下奇数
                .forEach(System.out::println);  // 1,3,5,7,9
    }
}

filter()方法接收Predicate接口对象,其定义的test()方法,负责判断元素是否符合条件,常用匿名函数体内写布尔表达式实现

@FunctionalInterface
public interface Predicate<T> {
    // 判断元素t是否符合条件:
    boolean test(T t);
}

四、使用reduce

map()filter()Stream的转换方法,而Stream.reduce()则是Stream的一个聚合方法,把Stream对象的所有元素按照聚合函数聚合成一个结果

import java.util.stream.*;
public class Main {
    public static void main(String[] args) {
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                                            .reduce(0, (acc, n) -> acc + n);
                                            // 参数依次是
                                            // 0:首次操作对象,初始值
                                            // 第一个参数acc:上次操作的返回值
                                            // 第二个参数n:该次的元素
        System.out.println(sum); // 45
    }
}

reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次操作的结果和本次的元素 进行二元操作,并返回操作的结果,以此迭代

原接口:

@FunctionalInterface
public interface BinaryOperator<T> {
    // Bi操作:两个输入,一个输出
    T apply(T t, T u);
}

Stream的元素个数为0时,不能正常调用reduce()的聚合函数(二元函数),

为此,可以先不传初始值,获得一个Optional对象,进而判断结果是否存在

Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
if (opt.isPresent()) {
    System.out.println(opt.get());
}

见下

Integer[][] nss={{},{1,2,3,4,5}};
for (Integer[] ns:nss) {
    var option = Arrays.stream(ns).reduce(Integer::sum);
    if (option.isPresent()) {
        int sum = Arrays.stream(ns).reduce(0, Integer::sum);
        System.out.println(sum);
    }
}

五、转换与聚合

本质上是时间和空间上的选择,依据现有情况,决定时间优先还是空间优先

  • 转换操作把一个Stream转换为另一个Stream,例如map()filter()

    转换操作不会触发任何计算,只能说保存了计算规则,懒加载

  • 聚合操作对Stream的每个元素进行计算,得到一个确定的结果,例如reduce()

    聚合操作会立刻促使Stream输出它的每一个元素,并依次纳入计算

输出为List

Stream的元素保存到集合,例如List,是一个聚合操作

使用collect() 方法,传入Collectors.toList()对象,可把Stream收集到List

类似的,collect(Collectors.toSet())可以把Stream的每个元素收集到Set

import ...
public class Main {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Apple", "", null, "Pear", "  ", "Orange");
        List<String> list = stream.filter(s -> s != null && !s.isBlank())
                                                            .collect(Collectors.toList());
        System.out.println(list);
    }
}

输出为数组

StreamtoArray()方法,并传入数组的“构造方法”,目的是指定类型

List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);

输出为Map

对于每个元素,添加到Map时需要key和value,为此,需要指定两个映射函数,分别把元素映射为key和value

import ...
public class Main {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
        Map<String, String> map = stream
                .collect(Collectors.toMap(
                        // 把元素s映射为key:
                        s -> s.substring(0, s.indexOf(':')),
                        // 把元素s映射为value:
                        s -> s.substring(s.indexOf(':') + 1)));
        System.out.println(map);
    }
}

本文由 tazume-sans 原创,转载请注明出处。

相关推荐