Java Stream API 全面指南(完整版)

发布于:2025-03-21 ⋅ 阅读:(19) ⋅ 点赞:(0)

Java Stream API 全面指南(完整版)


一、基础概念

1.1 什么是 Stream API?

  • 定义:Java 8 引入的 java.util.stream 包,提供函数式编程风格的流式操作,用于高效处理集合、数组等数据源。
  • 核心特性
    • 惰性计算:中间操作不会立即执行,直到终端操作触发。
    • 无存储:Stream 本身不存储数据,而是从数据源(如集合、数组)获取数据。
    • 不可复用:终端操作执行后,流对象失效,无法再次使用。
    • 函数式:操作不修改源数据,而是返回新流。

1.2 Stream 与 Collection 的区别

特性 Stream Collection
数据存储 不存储数据,仅作为计算管道 存储数据的内存结构
操作效果 不修改源数据,返回新流 直接修改数据结构
执行时机 延迟执行(终端操作触发) 立即执行
可复用性 一次消费,终端操作后失效 可多次遍历

二、Stream 的创建方式

2.1 常见创建方法

从集合创建
List<String> list = Arrays.asList("JAVA", "J2EE", "Spring");
// 返回顺序流(Stream<String>)
Stream<String> stream = list.stream();  
// 返回并行流(ParallelStream<String>)
Stream<String> parallelStream = list.parallelStream();
从数组创建
int[] array = {1, 2, 3, 4};
// 返回 IntStream(基本类型流)
IntStream intStream = Arrays.stream(array);  
其他数据源
// 读取文件行(返回 Stream<String>)
Stream<String> lines = Files.lines(Paths.get("file.txt"));  
// 创建零散数据的流(返回 Stream<String>)
Stream<String> customStream = Stream.of("a", "b", "c");  
// 返回空流(Stream.empty())
Stream<String> emptyStream = Stream.empty();  
双列集合(如 Map)
Map<String, Integer> map = new HashMap<>();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

三、核心操作

3.1 中间操作(Intermediate Operations)

1. 过滤 filter
List<String> filtered = list.stream()
    .filter(s -> s.length() > 3)  // 过滤长度大于3的字符串
    .collect(Collectors.toList());  
// 结果:["J2EE", "Spring"](假设原列表是 ["JAVA", "J2EE", "Spring"])
2. 映射 map
List<Integer> lengths = list.stream()
    .map(String::length)          // 将字符串转换为长度
    .collect(Collectors.toList());  
// 结果:[4, 5, 6](对应 "JAVA" (4), "J2EE" (5), "Spring" (6))
3. 排序 sorted
List<String> sorted = list.stream()
    .sorted()                     // 自然排序(按字母顺序)
    .collect(Collectors.toList());  
// 结果:["JAVA", "J2EE", "Spring"](假设原列表已有序)
4. 扁平化 flatMap
List<List<Integer>> listOfLists = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> flattened = listOfLists.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());  
// 结果:[1, 2, 3, 4]
5. 去重 distinct
List<Integer> distinct = Arrays.asList(1, 2, 2, 3).stream()
    .distinct()                  // 去重(依赖 equals() 和 hashCode())
    .collect(Collectors.toList());  
// 结果:[1, 2, 3]

3.2 终端操作(Terminal Operations)

1. 收集结果 collect
// 收集为 List(返回 List<String>)
List<String> result = list.stream().collect(Collectors.toList());  
// 分组(返回 Map<Character, List<String>>)
Map<Character, List<String>> grouped = list.stream()
    .collect(Collectors.groupingBy(s -> s.charAt(0)));  
// 结果:{J=[JAVA, J2EE], S=[Spring]}
2. 归约 reduce
// 求和(返回 Integer)
int sum = Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);  
// 结果:6
// 字符串拼接(返回 Optional<String>)
Optional<String> joined = Stream.of("a", "b", "c")
    .reduce((a, b) -> a + b);  
// 结果:Optional["abc"]
3. 遍历 forEach
list.stream().forEach(System.out::println);  
// 输出:
// JAVA
// J2EE
// Spring
4. 匹配与统计
4.1 anyMatch:检查是否存在匹配元素
// 检查是否有员工薪资 > 6000
boolean hasHighEarner = Emp.getEmployees().stream()
    .anyMatch(emp -> emp.getSalary() > 6000);  
// 结果:true(Charlie 的薪资为 7000)
4.2 allMatch:检查所有元素是否匹配
// 检查所有员工薪资是否 > 5000
boolean allHigh = Emp.getEmployees().stream()
    .allMatch(emp -> emp.getSalary() > 5000);  
// 结果:false(Bob 的薪资为 4500)
4.3 noneMatch:检查是否没有元素匹配
// 检查是否有员工薪资 < 4000
boolean noLowEarner = Emp.getEmployees().stream()
    .noneMatch(emp -> emp.getSalary() < 4000);  
// 结果:true(所有薪资 ≥ 4500)
4.4 findFirst:获取第一个元素
// 按姓名排序后获取第一个员工
Optional<Emp> firstEmp = Emp.getEmployees().stream()
    .sorted(Comparator.comparing(Emp::getName))
    .findFirst();  
// 结果:Optional[Emp{id=1, name='Alice', salary=6000}]
4.5 findAny:获取任意一个匹配元素
// 并行流中获取任意一个薪资 > 5000 的员工
Optional<Emp> anyEmp = Emp.getEmployees().parallelStream()
    .filter(emp -> emp.getSalary() > 5000)
    .findAny();  
// 结果:可能返回 Alice 或 Charlie(并行流结果可能不固定)

四、高级用法与最佳实践

4.1 并行流(Parallel Streams)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int parallelSum = numbers.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();  
// 结果:15(并行计算)
注意:
  • 线程安全:使用 Collectors.toConcurrentMap 代替 toMap
Map<String, Integer> concurrentMap = list.stream()
    .collect(Collectors.toConcurrentMap(
        s -> s, 
        s -> s.length()
    ));  
// 结果:线程安全的 Map(如 ConcurrentHashMap)

4.2 自定义收集器(Collector)

// 自定义收集器:将元素收集到 ArrayList
Collector<String, List<String>, List<String>> customCollector = 
    Collector.of(
        ArrayList::new,               // 供应商函数(创建初始容器)
        (list, element) -> list.add(element), // 累加器(添加元素)
        (list1, list2) -> {           // 组合器(合并两个容器)
            list1.addAll(list2); 
            return list1; 
        },
        list -> list                  // 结果转换器(返回最终结果)
    );

List<String> collected = list.stream()
    .collect(customCollector);  
// 结果:与原列表相同(假设原列表是 ["JAVA", "J2EE", "Spring"])

五、常见问题与注意事项

5.1 distinct() 的依赖

class Person {
    String name;
    // 未重写 equals() 和 hashCode() 方法
}
List<Person> people = Arrays.asList(new Person("Alice"), new Person("Alice"));
List<Person> distinctPeople = people.stream()
    .distinct()
    .collect(Collectors.toList());  
// 结果:可能包含重复项(因未重写 equals 和 hashCode)

5.2 并行流线程安全

// 错误示例(非线程安全的 Collectors.toMap)
Map<String, Integer> unsafeMap = list.parallelStream()
    .collect(Collectors.toMap(s -> s, s -> s.length()));  
// 可能抛出 ConcurrentModificationException

// 正确示例(线程安全)
Map<String, Integer> safeMap = list.parallelStream()
    .collect(Collectors.toConcurrentMap(s -> s, s -> s.length()));  
// 结果:线程安全的 Map(如 ConcurrentHashMap)

六、完整示例代码

6.1 复杂数据处理(使用自定义类)

class Emp {
    int id;
    String name;
    double salary;
    String sex;

    public Emp(int id, String name, double salary, String sex) {
        this.id = id;
        this.name = name;
        this.salary = salary;
        this.sex = sex;
    }

    // 新增 getSalary 方法
    public double getSalary() {
        return salary;
    }

    // 静态方法模拟数据
    public static List<Emp> getEmployees() {
        return Arrays.asList(
            new Emp(1, "Alice", 6000, "男"),
            new Emp(2, "Bob", 4500,"男"),
            new Emp(3, "Charlie", 7000, "女")
        );
    }
}
6.1.1 过滤高薪员工
// 过滤薪资 > 5000 的员工,按薪资降序排序
List<Emp> highEarners = Emp.getEmployees().stream()
    .filter(emp -> emp.getSalary() > 5000)
    .sorted((a, b) -> Double.compare(b.getSalary(), a.getSalary()))
    .collect(Collectors.toList());
// 结果:
// [
//   Emp{id=3, name='Charlie', salary=7000, sex="女"},
//   Emp{id=1, name='Alice', salary=6000, sex="男"}
// ]
6.1.2 groupingBy: 分组与统计
// 按姓名分组,统计薪资总和、平均值等
Map<String, DoubleSummaryStatistics> salaryStats = Emp.getEmployees().stream()
    .collect(Collectors.groupingBy(
        Emp::getName,
        Collectors.summarizingDouble(Emp::getSalary)
    ));
// 结果:
// {
//   "Alice" = {count=1, sum=6000.0, average=6000.0, min=6000.0, max=6000.0},
//   "Bob" = {count=1, sum=4500.0, average=4500.0, min=4500.0, max=4500.0},
//   "Charlie" = {count=1, sum=7000.0, average=7000.0, min=7000.0, max=7000.0}
// }
p -> emp.getSalary() > 6000);  
// 结果:true(Charlie 的薪资为 7000)
6.1.3 partitioningBy:按条件分区

Collectors.partitioningBy() 根据布尔条件将流元素分为两个组,返回 Map<Boolean, List<T>>

示例:按薪资是否高于 6000 分区

// 将员工分为薪资 > 6000 和 ≤ 6000 的两组
Map<Boolean, List<Emp>> partitioned = Emp.getEmployees().stream()
    .collect(Collectors.partitioningBy(emp -> emp.getSalary() > 6000));

// 结果:
// {
//   true = [Emp{id=3, name='Charlie', salary=7000, sex="女"}],
//   false = [Emp{id=1, name='Alice', salary=6000, sex="男"}, Emp{id=2, name='Bob', salary=4500, sex="男"}]
// }
6.1.4 嵌套分区与分组(如按薪资是否高于 6000,再按性别分组):
// 先按薪资分区,再按性别分组
Map<Boolean, Map<String, List<Emp>>> partitionedAndGrouped = Emp.getEmployees().stream()
    .collect(Collectors.partitioningBy(
        emp -> emp.getSalary() > 6000,
        Collectors.groupingBy(Emp::getSex)
    ));

// 结果:
// {
//   true = {Charlie=[Emp{id=3, name='Charlie', salary=7000, sex="女"}]},
//   false = {Alice=[...], Bob=[...]}
// }

七、总结

7.1 核心优势

  • 简化操作:通过链式调用减少代码量。
  • 高效处理:支持并行计算(如大数据场景)。
  • 函数式编程:避免副作用,代码更易维护。

7.2 注意事项

  • 线程安全:并行流需使用线程安全的收集器。
  • 对象规范:使用 distinct() 时必须重写 equals()hashCode()
  • 不可复用性:终端操作后流对象失效,需重新创建。

7.3 分区与分组的区别

特性 partitioningBy groupingBy
分组依据 布尔条件(返回 true/false) 任意键(如 String、Integer 等)
返回结果 Map<Boolean, List<T>> Map<K, List<T>>
适用场景 二元条件判断(如是否达标) 多元分组(如按性别、地区分组)

关键补充说明:

  1. Emp 类的 getSalary 方法:在 Emp 类中新增 getSalary() 方法,确保 Emp::getSalary 可用。
  2. 常用终端操作示例:补充了 anyMatchallMatchnoneMatchfindFirstfindAny 的完整代码和结果说明。
  3. 并行流的不确定性findAny 在并行流中的结果可能因线程执行顺序不同而变化,需注意其非确定性。
  4. . partitioningBy 的核心作用:将流元素按条件分为两组,适用于简单判断场景。
    • 嵌套使用:可结合 groupingBy 实现多级分组(如分区后进一步分组)。
    • 空值处理:即使无元素满足条件,仍会返回空列表(而非 null)。

网站公告

今日签到

点亮在社区的每一天
去签到