5971 字
30 min
0

Stream API

本文介绍 Java 8 引入的 Stream API,包括流的创建、常用操作(过滤、映射、排序、聚合)及并行流的使用,帮助开发者高效处理集合数据。

Stream API概述

什么是 Stream API

  1. Java 8 引入:从 JDK 1.8 开始,Java 推出了 Stream API,将函数式编程思想引入 Java。
  2. 声明式操作集合:Stream API 支持声明式操作,可以对集合进行过滤、映射、排序等处理,无需手动编写循环。
  3. 不修改原始数据源:Stream 操作返回新的数据流,原始集合保持不变,保证数据安全。
  4. 顺序与并行处理:Stream 可顺序执行,也可使用并行流利用多核 CPU 并行处理,提高大数据处理效率。

总结:Stream API 提供高效、灵活、可组合的数据处理方式,使 Java 代码更简洁、易读、易维护

import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
 
public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
 
        List<String> result = names.stream()
                                   .filter(name -> name.startsWith("A"))
                                   .collect(Collectors.toList());
 
        System.out.println(result); // 输出: [Alice]
    }
}
 

Stream 和 Collection

  1. Collection:强调数据存储,是静态的内存数据结构,比如 ListSetMap
  2. Stream:强调计算操作,是对集合进行处理的“数据流”,不存储数据,只做中间计算。

总结:Collection 面向内存,用来存储和管理数据;Stream 面向 CPU,用来处理数据、生成结果

Stream 的核心特点

  1. 不存储数据:Stream 只负责计算数据流,不保存元素本身。
  2. 不修改数据源:操作返回新的 Stream,原始集合保持不变。
  3. 延迟执行:中间操作如 filtermap惰性执行的,只有终止操作(如 collectforEach)才触发计算。
  4. 一次性使用Stream 只能使用一次,使用过后必须重新创建,否则会抛出 IllegalStateException
  5. 顺序流与并行流:顺序流stream(),单线程顺序处理。并行流parallelStream(),自动拆分数据,多线程处理。注意:并行流可能改变元素处理顺序,避免在并行流中使用共享可变状态。
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        // 创建流,中间操作 map 并未执行
        Stream<String> stream1 = Stream.of("A", "B", "C")
                .map(s -> {
                    System.out.println("map: " + s);
                    return s.toLowerCase();
                });
 
        System.out.println("=========");
 
        // 第一次终止操作,触发流计算
        stream1.forEach(System.out::println);
 
        System.out.println("=========");
 
        // 流已经被消费完,重新创建流才能再次使用
        Stream<String> stream2 = Stream.of("A", "B", "C")
                .map(s -> {
                    System.out.println("map: " + s);
                    return s.toLowerCase();
                });
 
        // 第二次终止操作
        stream2.forEach(System.out::println);
    }
}
 

Stream 的操作流程

操作流程:数据源 → 中间操作 → 中间操作 → … → 终止操作 → 结果

  1. 创建 Stream:从集合、数组、文件、生成器等生成 Stream。

    List<String> list = Arrays.asList("Java", "Python", "C++");
    Stream<String> stream = list.stream();
     
  2. 中间操作(Intermediate):返回新的 Stream,可链式调用,实现过滤、映射、排序等操作。

    Stream<String> filtered = stream
      .filter(s -> s.length() > 3)
      .map(String::toUpperCase);
     
  3. 终止操作(Terminal):触发计算并返回最终结果,例如: collectforEachcount

    List<String> result = filtered
      .collect(Collectors.toList());
     
    result.forEach(System.out::println);
     

创建 Stream 的方式

从 Collection 创建

最常见的方式,几乎所有集合类都支持,生成的 Stream 可以顺序或并行处理集合元素。

import java.util.*;
import java.util.stream.*;
 
public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "Python", "C++");
 
        // 顺序流
        Stream<String> sequentialStream = list.stream();
        System.out.println("顺序流输出:");
        sequentialStream.forEach(System.out::println);
 
        // 并行流
        Stream<String> parallelStream = list.parallelStream();
        System.out.println("并行流输出:");
        parallelStream.forEach(System.out::println);
    }
}
 

从数组创建

适合对数组元素进行流式操作,既可以用 Arrays.stream(),也可以用 Stream.of()

import java.util.stream.Stream;
import java.util.Arrays;
 
public class Main {
    public static void main(String[] args) {
        String[] arr = {"A", "B", "C"};
 
        // 使用 Arrays.stream()
        Stream<String> stream1 = Arrays.stream(arr);
        System.out.println("使用 Arrays.stream():");
        stream1.forEach(System.out::println);
 
        // 使用 Stream.of()
        Stream<String> stream2 = Stream.of(arr);
        System.out.println("使用 Stream.of():");
        stream2.forEach(System.out::println);
    }
}
 

使用 Stream 静态方法

  1. Stream.of():可创建任意元素的 Stream。
  2. Stream.empty(): 可创建空流。
  3. Stream.iterate():生成可迭代的无限流。
  4. Stream.generate():生成随机或固定逻辑的无限流。
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        // 创建包含指定元素的流
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        System.out.println("Stream.of元素流:");
        stream.forEach(System.out::println);
 
        System.out.println("=========");
 
        // 创建空流
        Stream<String> emptyStream = Stream.empty();
        System.out.println("空流:");
        emptyStream.forEach(System.out::println); // 不输出任何内容
 
        System.out.println("=========");
 
        // 无限流:generate
        Stream<Double> randomNumbers = Stream
                .generate(Math::random)
                .limit(5);
        System.out.println("无限流 generate (前5个随机数):");
        randomNumbers.forEach(System.out::println);
 
        System.out.println("=========");
 
        // 无限流:iterate
        Stream<Integer> naturalNumbers = Stream
                .iterate(1, n -> n + 1)
                .limit(10);
        System.out.println("无限流 iterate (前10个自然数):");
        naturalNumbers.forEach(System.out::println);
    }
}
 

从文件或字符串创建

适合读取外部数据源,生成每行或每个元素的 Stream。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        // -------------------------------
        // 1. 从字符串创建 Stream(方式1:split + Arrays.stream)
        // -------------------------------
        String str = "apple,banana,orange";
        Stream<String> strStream1 = Arrays.stream(str.split(","));
        System.out.println("从字符串创建 Stream(split + Arrays.stream):");
        strStream1.forEach(System.out::println);
 
        System.out.println("=========");
 
        // -------------------------------
        // 2. 从字符串创建 Stream(方式2:Pattern.splitAsStream)
        // -------------------------------
        Stream<String> strStream2 = Pattern.compile(",").splitAsStream(str);
        System.out.println("从字符串创建 Stream(Pattern.splitAsStream):");
        strStream2.forEach(System.out::println);
 
        System.out.println("=========");
 
        // -------------------------------
        // 3. 从文件或网络读取创建 Stream
        // -------------------------------
        String urlStr = "https://raw.githubusercontent.com/github/gitignore/main/Java.gitignore"; // 示例小文本
 
        try {
            URL url = new URL(urlStr);
 
            // BufferedReader.lines() 返回 Stream<String>
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
                Stream<String> lines = reader.lines();
                System.out.println("从网络读取文本创建 Stream:");
                lines.forEach(System.out::println);
            }
 
        } catch (IOException e) {
            System.out.println("读取网络文本失败: " + e.getMessage());
        }
    }
}
 

空流或 null 安全流

在集合或对象可能为 null 时,使用空流或 Optional 可避免空指针异常,安全创建 Stream。Optional.ofNullable(obj).stream() 可以将可能为 null 的对象安全地转换为 Stream,方便后续链式操作。

import java.util.stream.Stream;
import java.util.Optional;
 
public class Main {
    public static void main(String[] args) {
        // 创建空流
        Stream<Object> emptyStream = Stream.empty();
        System.out.println("空流内容:");
        emptyStream.forEach(System.out::println);
 
        System.out.println("=========");
 
        // 使用 Optional 创建 null 安全流
        String value = null; // 可以改成非 null 测试
        Stream<String> safeStream = Optional.ofNullable(value).stream();
        System.out.println("Optional 创建的 null 安全流内容:");
        safeStream.forEach(System.out::println);
    }
}
 

顺序流与并行流

  1. 顺序流:单线程顺序处理元素,简单直观,适合小数据集。
  2. 并行流:将数据拆分多线程处理,提升大数据处理性能,但需注意线程安全。
  3. 并行流并不总比顺序流快,适合 CPU 密集型操作和大数据量场景
import java.util.Arrays;
import java.util.List;
 
public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
 
        // 顺序流
        System.out.println("顺序流输出:");
        numbers.stream().forEach(System.out::println);
 
        System.out.println("=========");
 
        // 并行流
        System.out.println("并行流输出:");
        numbers.parallelStream().forEach(System.out::println);
    }
}
 

Stream 中间操作

中间操作是惰性执行的,返回新的 Stream,可链式调用。

操作JDK作用与说明使用场景 / 注意事项
filter8按条件筛选元素,返回满足条件的 Stream条件筛选,避免循环手动判断
map8一对一映射,每个元素映射为一个新元素类型转换、字段提取、计算
flatMap8一对多映射,扁平化 Stream展开嵌套集合(List → List)
distinct8去重,返回不重复元素使用 equals() 判断重复,注意性能
sorted8排序,默认自然顺序或自定义 Comparator默认自然顺序,需要实现 Comparable
limit8截取前 N 个元素分页或取前几条数据
skip8跳过前 N 个元素分页或跳过不需要的元素
peek8遍历元素并执行动作,通常用于调试不改变元素内容,用于打印、日志
boxed8将基本类型流转为对象流IntStream → Stream 等
asIntStream /
asLongStream /
asDoubleStream
8将对象流转为基本类型流减少装箱开销,数值计算
takeWhile9顺序流中,从头取元素直到条件不满足停止顺序流明确,遇第一个不满足停止
dropWhile9顺序流中,从头丢弃满足条件元素顺序流明确,遇第一个不满足保留后续
filterNot / filterNotNull9 / 10 (扩展库)filterNot 条件取反,filterNotNull 过滤 null官方 Java 无直接实现,多见于 Kotlin / Vavr / Guava
mapMulti21高性能一对多映射,替代 flatMap避免中间 Stream 创建,大数据扁平化
takeUntil / dropUntil21类似 takeWhile / dropWhile,但条件满足/不满足停止顺序流条件灵活控制

筛选 filter

根据条件筛选元素,返回满足条件的 Stream。

import java.util.Arrays;
import java.util.List;
 
public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "ab", "abc");
 
        list.stream()
            .filter(s -> s.length() == 2) // 筛选长度为 2 的元素
            .forEach(System.out::println); // 输出: ab
    }
}
 

映射 map

一对一转换,每个输入元素对应一个输出元素,返回新的 Stream。

import java.util.Arrays;
import java.util.List;
 
public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "ab", "abc");
 
        list.stream()
            .map(String::toUpperCase) // 将元素转换为大写
            .forEach(System.out::println); // 输出: A AB ABC
    }
}
 

扁平化 flatMap

一对多映射,将每个元素映射为 Stream 并合并成一个 Stream。flatMap可以把多层嵌套结构展开,避免出现 Stream<Stream<T>>

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
              .flatMap(List::stream) // 扁平化子列表
              .forEach(System.out::println); // 输出: 1 2 3 4
    }
}
 

除重 distinct

使用 equals() 方法判断元素是否重复,然后去重,返回不重复的元素流。会产生额外开销,数据量大时注意性能。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of(1, 2, 2, 3)
              .distinct() // 去重
              .forEach(System.out::println); // 输出: 1 2 3
    }
}
 

排序 sorted

对 Stream 元素进行排序,可使用自然顺序或自定义比较器。默认按元素的自然顺序排序,需要实现 Comparable。自定义排序可传入 Comparator

import java.util.Comparator;
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        System.out.println("自然顺序排序:");
        Stream.of(3, 1, 2)
              .sorted() // 自然顺序
              .forEach(System.out::println); // 输出: 1 2 3
 
        System.out.println("=========");
 
        System.out.println("反序排序:");
        Stream.of(3, 1, 2)
              .sorted(Comparator.reverseOrder()) // 反序
              .forEach(System.out::println); // 输出: 3 2 1
    }
}
 

截断 limit / 跳过 skip

limit(n):截取前 n 个元素。skip(n):跳过前 n 个元素。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 5)
              .skip(2)   // 跳过前两个元素
              .limit(2)  // 取接下来的两个元素
              .forEach(System.out::println); // 输出: 3 4
    }
}
 

peek

对 Stream 中的每个元素执行动作,不建议用于修改元素内容,主要用于调试和观察中间状态。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of("apple", "banana", "avocado")
              .filter(s -> s.startsWith("a"))
              .peek(s -> System.out.println("中间元素: " + s)) // 查看中间元素
              .map(String::toUpperCase)
              .forEach(s -> System.out.println("最终输出: " + s));
    }
}
 

基本类型流转换

基本类型流转换 boxed / asIntStream / asLongStream / asDoubleStream,将基本类型流和对象流互相转换,减少装箱开销或方便链式操作。

import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        IntStream.range(1, 5)
                 .boxed() // IntStream -> Stream<Integer>
                 .forEach(System.out::println); // 输出: 1 2 3 4
      	
      	System.out.println("=========");
      
      	Stream.of("1", "2", "3")
              .mapToInt(Integer::parseInt) // Stream<String> -> IntStream
              .forEach(System.out::println); // 输出: 1 2 3
    }
}
 

条件取元素 takeWhile

从 Stream 开头取元素,直到第一个不满足条件的元素为止。只作用于顺序流(Sequential Stream)时行为明确。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 1, 2)
              .takeWhile(n -> n < 4) // 条件满足时取元素
              .forEach(System.out::println); // 输出: 1 2 3
    }
}
 

条件丢弃元素 dropWhile

从 Stream 开头丢弃满足条件的元素,直到第一个不满足条件的元素后保留剩余元素。也只作用于顺序流时效果一致。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 1, 2)
              .dropWhile(n -> n < 4) // 条件满足时丢弃元素
              .forEach(System.out::println); // 输出: 4 1 2
    }
}
 

高性能一对多映射 mapMulti

JDK21引入,替代 flatMap,避免创建中间 Stream。

import java.util.stream.Stream;
 
public class Main {
    public static void main(String[] args) {
        Stream.of("苹果", "香蕉")
              .mapMulti((str, consumer) -> {
                  for (char c : str.toCharArray()) {
                      consumer.accept(c); // 输出每个字符
                  }
              })
              .forEach(System.out::println); // 输出: a p p l e b a n a n a
    }
}
 

Stream 终止操作

执行终止操作会触发 Stream 的计算,Stream 不可复用。

操作返回值功能与说明
forEachvoid遍历每个元素并执行动作,常用于打印或输出
toArray数组将 Stream 元素收集到数组中
reduceOptional / T / U归约操作,将元素合并为一个值(求和、求最大值等)
collect任意收集 Stream 元素到集合、Map 或自定义容器
countlong返回元素数量
anyMatchboolean判断是否至少有一个元素匹配条件
allMatchboolean判断是否所有元素都匹配条件
noneMatchboolean判断是否没有元素匹配条件
findFirstOptional返回第一个元素
findAnyOptional返回任意一个元素(顺序流通常是第一个,并行流可能是任意)
max/minOptional返回最大值或最小值

遍历 forEach

  1. 遍历每个元素并执行指定动作,常用于打印、日志或其他副作用操作。
  2. 不能返回值。
  3. 对并行流,元素处理顺序可能不固定。
import java.util.List;
import java.util.Arrays;
 
public class StreamForEachDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "Python", "C++");
 
        // 遍历输出每个元素
        list.stream()
            .forEach(System.out::println);
    }
}
 

匹配 Match & Find

  1. 匹配操作用于判断 Stream 中元素是否满足指定条件,或者获取特定元素。
  2. allMatchanyMatchnoneMatch 短路操作,一旦结果确定就会停止遍历,性能高。
  3. findFirst 在顺序流中返回第一个元素,在并行流中也保证顺序;findAny 在并行流中可能返回任意元素。
  4. 返回值为 Optional 的方法应避免直接 get(),推荐使用 ifPresentorElse 避免空指针异常。
方法返回值功能说明
allMatchboolean判断是否所有元素都匹配条件
anyMatchboolean判断是否至少有一个元素匹配条件
noneMatchboolean判断是否没有元素匹配条件
findFirstOptional获取第一个元素
findAnyOptional获取任意一个元素(并行流可能不同)

import java.util.List;
import java.util.Arrays;
import java.util.Optional;
 
public class StreamMatchFindDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
 
        // 1. 检查是否所有名字长度大于 2
        boolean all = list.stream()
                          .allMatch(name -> name.length() > 2);
        System.out.println("是否所有名字长度大于 2:" + all);
 
        // 2. 检查是否至少有一个名字以 'A' 开头
        boolean any = list.stream()
                          .anyMatch(name -> name.startsWith("A"));
        System.out.println("是否至少有一个名字以 A 开头:" + any);
 
        // 3. 检查是否没有名字为空
        boolean none = list.stream()
                           .noneMatch(String::isEmpty);
        System.out.println("是否没有名字为空:" + none);
 
        // 4. 获取第一个名字
        Optional<String> first = list.stream().findFirst();
        first.ifPresent(s -> System.out.println("第一个名字:" + s));
 
        // 5. 获取任意一个名字(并行流可能随机)
        Optional<String> anyName = list.parallelStream().findAny();
        anyName.ifPresent(s -> System.out.println("任意一个名字:" + s));
 
        // 6. 跳过前 3 个名字,获取第 4 个
        Optional<String> fourth = list.stream().skip(3).findFirst();
        fourth.ifPresent(s -> System.out.println("第四个名字:" + fourth.get()));
    }
}
 

归约 reduce

  1. 归约(reduce)用于将 Stream 中的元素按照一定规则组合,最终生成一个值。
  2. 在 Java Stream 接口中,常用归约方法如下:
    • Optional<T> reduce(BinaryOperator<T> accumulator):没有初始值,返回 Optional,适用于可能为空的流。
    • T reduce(T identity, BinaryOperator<T> accumulator):提供初始值,返回类型与元素类型相同,适用于有初始值的累加或累乘等操作。
  3. 常见的 max()min()count() 也可以视作特殊的归约操作:
    • long count():计算元素数量
    • Optional<T> max(Comparator<? super T> comparator):获取最大元素
    • Optional<T> min(Comparator<? super T> comparator):获取最小元素
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
 
public class StreamReduceDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
 
        // 1. 所有元素相加
        Integer sum = numbers.stream()
                             .reduce(Integer::sum)  // 等同于 (x, y) -> x + y
                             .orElse(0);
        System.out.println("总和:" + sum); // 输出: 15
 
        // 2. 所有元素相乘
        Integer product = numbers.stream()
                                 .reduce((x, y) -> x * y)
                                 .orElse(1);
        System.out.println("乘积:" + product); // 输出: 120
 
        // 3. 找到最大元素
        Integer max = numbers.stream()
                             .reduce(Integer::max)
                             .orElse(null);
        System.out.println("最大值:" + max); // 输出: 5
 
        // 4. 找到最小元素
        Integer min = numbers.stream()
                             .reduce(Integer::min)
                             .orElse(null);
        System.out.println("最小值:" + min); // 输出: 1
 
        // 5. 使用初始值进行累加(例如初始值为 10)
        Integer sumWithInit = numbers.stream()
                                     .reduce(10, Integer::sum);
        System.out.println("总和(含初始值10):" + sumWithInit); // 输出: 25
 
        // 6. 使用 count() 获取元素数量
        long count = numbers.stream().count();
        System.out.println("元素数量:" + count); // 输出: 5
 
        // 7. 使用 max() 获取最大元素
        Optional<Integer> maxValue = numbers.stream().max(Integer::compareTo);
        maxValue.ifPresent(val -> System.out.println("最大值(max()):" + val));
 
        // 8. 使用 min() 获取最小元素
        Optional<Integer> minValue = numbers.stream().min(Integer::compareTo);
        minValue.ifPresent(val -> System.out.println("最小值(min()):" + val));
    }
}
 

收集 collect

collect 是最灵活的终止操作,可将 Stream 的元素归集到集合、Map等。

归集(toList/toSet/toMap)

  1. toList() / toSet() 仅返回接口类型(List / Set),具体实现不可控。
  2. 如果想明确集合类型,如 ArrayListLinkedListHashSetTreeSet,需使用 toCollection
import java.util.*;
import java.util.stream.Collectors;
 
public class StreamCollectDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "orange", "apple");
 
        // 转为 List
        List<String> list = words.stream()
                                 .collect(Collectors.toList());
        System.out.println("List: " + list);
 
        // 转为 Set(去重)
        Set<String> set = words.stream()
                               .collect(Collectors.toSet());
        System.out.println("Set: " + set);
 
        // 转为指定集合类型 - ArrayList
        ArrayList<String> arrayList = words.stream()
                                           .collect(Collectors.toCollection(ArrayList::new));
        System.out.println("ArrayList: " + arrayList);
 
        // 转为指定集合类型 - LinkedList
        LinkedList<String> linkedList = words.stream()
                                             .collect(Collectors.toCollection(LinkedList::new));
        System.out.println("LinkedList: " + linkedList);
 
        // 转为指定集合类型 - TreeSet(排序去重)
        TreeSet<String> treeSet = words.stream()
                                       .collect(Collectors.toCollection(TreeSet::new));
        System.out.println("TreeSet: " + treeSet);
 
        // 转 Map - key 为首字母,value 为单词
        Map<Character, String> map = words.stream()
                                         .collect(Collectors.toMap(
                                             w -> w.charAt(0),
                                             w -> w,
                                             (existing, replacement) -> existing // 遇到重复 key 时保留已有值
                                         ));
        System.out.println("Map: " + map);
    }
}
 

数组 toArray

  1. toArray() 返回 Object[]
  2. toArray(T[]::new) 可返回指定类型数组。
import java.util.*;
import java.util.stream.Collectors;
 
public class StreamToArrayDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "orange", "apple");
 
        // 转为 String 数组
        String[] array1 = words.stream()
                               .toArray(String[]::new);
        System.out.println("Array: " + Arrays.toString(array1));
 
        // 转为 Object 数组
        Object[] array2 = words.stream()
                               .toArray();
        System.out.println("Object Array: " + Arrays.toString(array2));
    }
}
 

分组 groupingBy

  1. 按指定规则对元素分组,返回 Map<K, List<T>>,K 为分组条件。
import java.util.*;
import java.util.stream.Collectors;
 
public class StreamGroupingDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "orange", "apricot");
 
        // 按首字母分组
        Map<Character, List<String>> grouped = words.stream()
                .collect(Collectors.groupingBy(w -> w.charAt(0)));
 
        System.out.println("Grouped: " + grouped);
    }
}
 

连接 joining

  1. 将元素按指定分隔符拼接为字符串,可指定前缀和后缀。
import java.util.*;
import java.util.stream.Collectors;
 
public class StreamJoiningDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "orange", "apple");
 
        // 使用 Collectors.joining 拼接字符串
        String joined = words.stream()
                .collect(Collectors.joining(", ", "[", "]"));
 
        System.out.println("Joined: " + joined); // 输出: [apple, banana, orange, apple]
    }
}
 

统计

Collectors提供了一系列用于数据统计的静态方法:

  1. 计数:counting
  2. 平均值:averagingIntaveragingLongaveragingDouble
  3. 最值:maxByminBy
  4. 求和:summingIntsummingLongsummingDouble
  5. 统计以上所有:summarizingIntsummarizingLongsummarizingDouble
import java.util.*;
import java.util.stream.Collectors;
 
public class StreamStatisticsDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
 
        // 计数
        long count = numbers.stream().collect(Collectors.counting());
        System.out.println("Count: " + count);
 
        // 求平均值
        double avg = numbers.stream().collect(Collectors.averagingInt(Integer::intValue));
        System.out.println("Average: " + avg);
 
        // 求总和
        long sum = numbers.stream().collect(Collectors.summingLong(Integer::longValue));
        System.out.println("Sum: " + sum);
 
        // 最大值
        Optional<Integer> max = numbers.stream().collect(Collectors.maxBy(Integer::compareTo));
        max.ifPresent(v -> System.out.println("Max: " + v));
 
        // 最小值
        Optional<Integer> min = numbers.stream().collect(Collectors.minBy(Integer::compareTo));
        min.ifPresent(v -> System.out.println("Min: " + v));
 
        // 总结统计信息
        IntSummaryStatistics stats = numbers.stream().collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println("Summary: " + stats);
    }
}
 

并行流与性能优化

并行流(Parallel Stream)通过多线程并行处理元素,可以提升 CPU 密集型任务的性能。

创建并行流

import java.util.Arrays;
 
public class ParallelStreamDemo {
    public static void main(String[] args) {
        // 方法1:通过集合 parallelStream() 创建并行流
        Arrays.asList(1, 2, 3, 4, 5, 6)
              .parallelStream()
              .forEach(System.out::println);
 
        // 方法2:将普通流转换为并行流
        Arrays.asList(1, 2, 3, 4, 5, 6)
              .stream()
              .parallel()
              .forEach(System.out::println);
    }
}
 

并行流的注意事项

  1. 适合 CPU 密集型操作
    • 大量计算、数据处理时,使用并行流可以显著提升性能。
    • I/O 密集型或小数据量时,并行开销可能大于收益。
  2. 避免共享可变状态
    • 并行流中多线程同时处理元素,如果修改共享变量可能导致线程安全问题。
    • 推荐使用无状态操作或线程安全容器。
  3. 减少中间操作冗余
    • 避免重复 distinct()sorted() 等操作,因为它们可能在并行流中开销更大。
  4. 顺序敏感操作慎用
    • forEachOrdered() 可以保证顺序,但会降低并行性能。

流式异常处理

Stream API 不像传统循环容易使用 try-catch,但仍可通过多种方式处理异常。

注意:

  • Stream 不允许抛出受检异常(Checked Exception),必须捕获或转换为运行时异常。
  • 处理异常时避免中断整个流,必要时可提供默认值或跳过。
  • 并行流中异常会在线程中抛出,需注意异常传播和日志记录。

  1. 方式1:在 lambda 内部捕获异常
import java.util.Arrays;
 
public class Main {
    public static void main(String[] args) {
        Arrays.asList("1", "2", "a", "3")
              .stream()
              .map(s -> {
                  try {
                      return Integer.parseInt(s);
                  } catch (NumberFormatException e) {
                      System.out.println("转换失败: " + s);
                      return 0; // 默认值或跳过处理
                  }
              })
              .forEach(System.out::println);
    }
}
 
  1. 方式2:封装受检异常(Checked Exception)
import java.util.Arrays;
import java.util.stream.Stream;
import java.io.IOException;
 
public class StreamCheckedExceptionDemo {
 
    public static void main(String[] args) {
        String[] paths = {"file1.txt", "file2.txt"};
 
        Arrays.stream(paths)
              .map(path -> {
                  try {
                      // 假设 readFile 可能抛 IOException
                      return readFile(path);
                  } catch (IOException e) {
                      e.printStackTrace();
                      return ""; // 出错返回空字符串
                  }
              })
              .forEach(System.out::println);
    }
 
    private static String readFile(String path) throws IOException {
        // 模拟读取文件
        if ("file2.txt".equals(path)) throw new IOException("读取失败");
        return "内容:" + path;
    }
}
 
  1. 方式3:将异常包装为 RuntimeException
Arrays.asList("1", "2", "a", "3")
      .stream()
      .map(s -> {
          try {
              return Integer.parseInt(s);
          } catch (NumberFormatException e) {
              throw new RuntimeException(e);
          }
      })
      .forEach(System.out::println);
 

相关文章

评论区