JDK 8 Stream API 教程文档

Stream 简介

Stream 是 JDK 8 中处理集合(Collection)数据的新抽象,它可以让你以声明式的方式处理数据,类似于使用 SQL 语句进行数据库查询。

List names = Arrays.asList("Alice", "Bob", "Charlie", "David");

java 复制代码
// 传统方式
List<String> result = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("A")) {
        result.add(name.toUpperCase());
    }
}

// Stream 方式
List<String> streamResult = names.stream()
    .filter(name -> name.startsWith("A"))
    .map(String::toUpperCase)
    .collect(Collectors.toList());

Stream 的特点

  1. 不是数据结构:Stream 不会存储数据,只是对数据源进行计算
  2. 不修改源数据:对 Stream 的操作不会影响原始数据源
  3. 惰性执行:中间操作是惰性的,只有在终止操作时才会执行
  4. 可消费性:Stream 只能被消费一次

Stream 操作分类

操作类型 方法示例 返回值 特点
中间操作 filter(), map(), sorted() Stream 惰性执行,可链式调用
终止操作 forEach(), collect(), count() 非Stream 立即执行,关闭流

创建 Stream

1. 从集合创建

java 复制代码
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();

2. 从数组创建

java 复制代码
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

3. 使用 Stream.of()

java 复制代码
Stream<String> stream = Stream.of("a", "b", "c");

4. 创建数值流

java 复制代码
IntStream intStream = IntStream.range(1, 5);     // 1,2,3,4
IntStream closedStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

5. 创建无限流

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

// 创建斐波那契数列
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
      .limit(10)
      .map(t -> t[0])
      .forEach(System.out::println);

中间操作

1. filter() - 过滤

java 复制代码
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> result = names.stream()
    .filter(name -> name.length() > 3)
    .collect(Collectors.toList());
// 结果: [Alice, Charlie, David]

2. map() - 映射

java 复制代码
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList());
// 结果: [5, 3, 7]

3. flatMap() - 扁平化映射

java 复制代码
List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);

List<String> flatList = listOfLists.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
// 结果: [a, b, c, d]

4. distinct() - 去重

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinctNumbers = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// 结果: [1, 2, 3]

5. sorted() - 排序

java 复制代码
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
List<String> sortedNames = names.stream()
    .sorted()
    .collect(Collectors.toList());
// 结果: [Alice, Bob, Charlie]

// 自定义排序
List<String> customSorted = names.stream()
    .sorted((a, b) -> b.length() - a.length())
    .collect(Collectors.toList());
// 结果: [Charlie, Alice, Bob]

6. limit() 和 skip() - 限制和跳过

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> limited = numbers.stream()
    .limit(5)
    .collect(Collectors.toList());
// 结果: [1, 2, 3, 4, 5]

List<Integer> skipped = numbers.stream()
    .skip(5)
    .collect(Collectors.toList());
// 结果: [6, 7, 8, 9, 10]

7. peek() - 查看元素(主要用于调试)

java 复制代码
List<String> result = Stream.of("one", "two", "three")
    .filter(s -> s.length() > 3)
    .peek(s -> System.out.println("Filtered value: " + s))
    .map(String::toUpperCase)
    .peek(s -> System.out.println("Mapped value: " + s))
    .collect(Collectors.toList());

终止操作

1. forEach() - 遍历

java 复制代码
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);

2. collect() - 收集

java 复制代码
// 转换为List
List<String> list = stream.collect(Collectors.toList());

// 转换为Set
Set<String> set = stream.collect(Collectors.toSet());

// 转换为Map
Map<String, Integer> map = stream.collect(
    Collectors.toMap(s -> s, String::length)
);

// 连接字符串
String joined = stream.collect(Collectors.joining(", "));

// 分组
Map<Integer, List<String>> grouped = names.stream()
    .collect(Collectors.groupingBy(String::length));

// 分区
Map<Boolean, List<String>> partitioned = names.stream()
    .collect(Collectors.partitioningBy(s -> s.length() > 3));

3. reduce() - 归约

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 求和
Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
// 或
Integer sum2 = numbers.stream().reduce(0, Integer::sum);

// 求最大值
Optional<Integer> max = numbers.stream().reduce(Integer::max);

// 字符串连接
Optional<String> concat = Stream.of("a", "b", "c").reduce(String::concat);

4. 匹配操作

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // false
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true
boolean noneEven = numbers.stream().noneMatch(n -> n % 2 == 0); // false

5. 查找操作

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

Optional<String> first = names.stream().findFirst();
Optional<String> any = names.stream().findAny();

6. 统计操作

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

long count = numbers.stream().count();
IntSummaryStatistics stats = numbers.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();

System.out.println("最大值: " + stats.getMax());
System.out.println("最小值: " + stats.getMin());
System.out.println("总和: " + stats.getSum());
System.out.println("平均值: " + stats.getAverage());

并行流

创建并行流

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

// 方法1:从集合创建
Stream<String> parallelStream = names.parallelStream();

// 方法2:将顺序流转为并行流
Stream<String> parallel = names.stream().parallel();

并行流示例

java 复制代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

long startTime = System.currentTimeMillis();
long sum = numbers.parallelStream()
    .mapToLong(Integer::longValue)
    .sum();
long endTime = System.currentTimeMillis();

System.out.println("总和: " + sum);
System.out.println("执行时间: " + (endTime - startTime) + "ms");

并行流注意事项

  1. 线程安全:确保操作是线程安全的
  2. 状态无关:避免有状态的lambda表达式
  3. 顺序敏感:某些操作(如findFirst)在并行流中性能可能更差
  4. 数据量:小数据量使用并行流可能适得其反

实战示例

示例1:员工数据处理

java 复制代码
class Employee {
    private String name;
    private String department;
    private double salary;
    private int age;
    
    // 构造方法、getter、setter省略
}

List<Employee> employees = Arrays.asList(
    new Employee("Alice", "IT", 5000, 25),
    new Employee("Bob", "HR", 4000, 30),
    new Employee("Charlie", "IT", 6000, 35),
    new Employee("David", "Finance", 5500, 28)
);

// 1. 按部门分组,计算每个部门的平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.averagingDouble(Employee::getSalary)
    ));

// 2. 找出IT部门工资最高的员工
Optional<Employee> highestPaidInIT = employees.stream()
    .filter(e -> "IT".equals(e.getDepartment()))
    .max(Comparator.comparingDouble(Employee::getSalary));

// 3. 按年龄排序,获取前3名员工
List<Employee> top3ByAge = employees.stream()
    .sorted(Comparator.comparingInt(Employee::getAge).reversed())
    .limit(3)
    .collect(Collectors.toList());

// 4. 统计每个部门的员工数量
Map<String, Long> employeeCountByDept = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment, 
        Collectors.counting()
    ));

示例2:文件处理

java 复制代码
// 读取文件并统计单词频率
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
    Map<String, Long> wordCount = lines
        .flatMap(line -> Arrays.stream(line.split("\s+")))
        .map(word -> word.replaceAll("[^a-zA-Z]", "").toLowerCase())
        .filter(word -> !word.isEmpty())
        .collect(Collectors.groupingBy(
            word -> word, 
            Collectors.counting()
        ));
    
    // 按频率排序
    wordCount.entrySet().stream()
        .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
        .limit(10)
        .forEach(entry -> 
            System.out.println(entry.getKey() + ": " + entry.getValue())
        );
} catch (IOException e) {
    e.printStackTrace();
}

示例3:复杂数据转换

java 复制代码
// 多层数据转换和聚合
class Order {
    private String customer;
    private List<OrderItem> items;
    private LocalDate orderDate;
    // 构造方法、getter、setter省略
}

class OrderItem {
    private String product;
    private int quantity;
    private double price;
    // 构造方法、getter、setter省略
}

List<Order> orders = // 初始化订单数据

// 计算每个客户的总消费金额
Map<String, Double> customerTotal = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getCustomer,
        Collectors.summingDouble(order -> 
            order.getItems().stream()
                .mapToDouble(item -> item.getQuantity() * item.getPrice())
                .sum()
        )
    ));

// 找出最畅销的产品
Optional<String> bestSellingProduct = orders.stream()
    .flatMap(order -> order.getItems().stream())
    .collect(Collectors.groupingBy(
        OrderItem::getProduct,
        Collectors.summingInt(OrderItem::getQuantity)
    ))
    .entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey);

最佳实践

  1. 优先使用方法引用:使代码更简洁
  2. 避免副作用:保持函数的纯粹性
  3. 合理使用并行流:根据数据量和操作复杂度决定
  4. 及时关闭资源:使用try-with-resources处理IO相关的Stream
  5. 合理使用Optional:避免空指针异常

总结

JDK 8 的 Stream API 为集合操作提供了强大的函数式编程能力,通过链式调用和惰性求值等特性,可以写出更简洁、易读、高效的代码。掌握 Stream API 对于现代 Java 开发至关重要。

相关推荐
番茄Salad3 小时前
Spring Boot项目,修改项目名称,修改包名!
java·spring boot·后端
疯狂的程序猴3 小时前
Fiddler调试工具全面解析 HTTPHTTPS抓包、代理设置与接口测试实战教程
后端
极市平台3 小时前
骁龙大赛技术分享第4期来了
人工智能·经验分享·笔记·后端·个人开发
开心就好20253 小时前
Charles抓包工具使用方法 Charles抓包分析、配置教程、网络排查技巧与手机抓包步骤
后端
sheji34163 小时前
【开题答辩全过程】以 基于springboot游泳馆管理系统为例,包含答辩的问题和答案
java·spring boot·后端
5***r9353 小时前
SpringBoot 与 SpringCloud的版本对应详细版
spring boot·后端·spring cloud
在人间负债3 小时前
昇腾 RAG SDK 从入门到实战:技术解析与部署实操
后端·算法
天天摸鱼的java工程师3 小时前
MySQL 的锁机制和数据隔离:一个 Java 老兵的实战总结
java·后端
undsky4 小时前
【RuoYi-SpringBoot3-Pro】:若依企业级增强版 —— 让开发更安全高效
spring boot·后端
踏浪无痕4 小时前
准备手写Simple Raft(四):日志终于能"生效"了
分布式·后端