Java 8 Stream API
培训手册

学习如何使用Java 8引入的强大Stream API简化数据处理,提高代码可读性和效率。本手册专为Java初学者和中级开发者设计,包含丰富的示例和最佳实践。

Java 8 Stream API

代码示例

50+ 实用示例

为什么使用 Stream API?

Stream API 是 Java 8 引入的一个革命性特性,它改变了我们处理集合数据的方式

更高效的代码

Stream API 允许你编写更简洁、更高效的代码,减少样板代码,提高生产力。

并行处理

轻松实现并行处理,充分利用多核处理器,提高大数据集的处理速度。

更易读的代码

声明式的编程风格使代码更易读,更符合现代编程范式。

传统方式 vs Stream API

传统方式

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = new ArrayList<>(); for (Integer number : numbers) { if (number % 2 == 0) { evenNumbers.add(number); } } System.out.println(evenNumbers);

Stream API 方式

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers);

核心概念

什么是 Stream?

Stream 是 Java 8 引入的一种新的抽象层,它允许你以声明式的方式处理数据集合。Stream 不是一个数据结构,而是对数据结构进行操作的工具。

关键特性:

  • 不存储数据,只是对数据源进行操作
  • 不改变原始数据源
  • 延迟执行,只有在需要结果时才会执行
  • 可以是无限的

Stream 操作类型

Stream 操作分为两种类型:中间操作和终端操作。一个 Stream 可以有多个中间操作,但只能有一个终端操作。

中间操作 (返回 Stream):

  • filter(), map(), sorted(), distinct(), limit(), skip()

终端操作 (返回结果):

  • forEach(), collect(), count(), reduce(), findFirst(), anyMatch()

惰性求值

Stream 操作是惰性的,这意味着中间操作不会立即执行,只有在调用终端操作时才会触发整个流水线的执行。

惰性求值的好处:

  • 避免不必要的计算
  • 支持无限流处理
  • 优化性能,特别是对于大数据集

并行流

Java 8 提供了并行流的支持,可以自动将数据分配到多个线程上进行处理,充分利用多核处理器的性能。

使用并行流:

  • 通过 parallelStream() 方法创建
  • 适用于大数据集和计算密集型任务
  • 注意线程安全问题
  • 并非所有场景都能提高性能

Stream 操作流程可视化

Stream操作流程

Stream 操作流程

Stream 操作通常包含三个步骤:创建流、中间操作和终端操作。中间操作可以链式调用,形成一个操作流水线。

Stream 操作详解

创建流

在使用 Stream API 之前,首先需要创建一个流。Java 提供了多种创建流的方式。

从集合创建

// 从 List 创建流 List<String> list = Arrays.asList("a", "b", "c"); Stream<String> stream = list.stream(); // 从 Set 创建流 Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3)); Stream<Integer> stream = set.stream();

从数组创建

// 从数组创建流 String[] array = {"a", "b", "c"}; Stream<String> stream = Arrays.stream(array); // 从部分数组创建流 Stream<String> stream = Arrays.stream(array, 1, 3); // 包含索引1,不包含索引3

使用 Stream.of()

// 直接创建流 Stream<String> stream = Stream.of("a", "b", "c"); // 创建空流 Stream<String> emptyStream = Stream.empty();

创建无限流

// 使用 iterate 创建无限流 Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2); // 取前5个偶数 evenNumbers.limit(5).forEach(System.out::println); // 使用 generate 创建无限流 Stream randomNumbers = Stream.generate(Math::random); // 取前3个随机数 randomNumbers.limit(3).forEach(System.out::println);

中间操作

中间操作返回一个新的流,允许你链式调用多个操作。这些操作是惰性的,只有在调用终端操作时才会执行。

filter() - 过滤元素

filter() 方法接受一个 Predicate 函数,返回一个只包含满足条件的元素的流。

示例:过滤偶数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // 输出: [2, 4, 6, 8, 10]
示例:过滤长度大于5的字符串
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry"); List<String> longWords = words.stream() .filter(word -> word.length() > 5) .collect(Collectors.toList()); System.out.println(longWords); // 输出: [banana, cherry, elderberry]

map() - 映射元素

map() 方法接受一个 Function 函数,将每个元素转换为另一个元素。

示例:将数字转换为平方
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList()); System.out.println(squares); // 输出: [1, 4, 9, 16, 25]
示例:将字符串转换为大写
List<String> words = Arrays.asList("hello", "world", "java"); List<String> upperCaseWords = words.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(upperCaseWords); // 输出: [HELLO, WORLD, JAVA]

flatMap() - 扁平化映射

flatMap() 方法用于处理嵌套集合,将每个元素转换为一个流,然后将所有流合并为一个流。

示例:扁平化嵌套列表
List<List<Integer>> nestedList = Arrays.asList( Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5, 6) ); List<Integer> flattenedList = nestedList.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(flattenedList); // 输出: [1, 2, 3, 4, 5, 6]
示例:处理字符串数组
List<String> words = Arrays.asList("Hello", "World"); List<String> characters = words.stream() .flatMap(word -> Arrays.stream(word.split(""))) .collect(Collectors.toList()); System.out.println(characters); // 输出: [H, e, l, l, o, W, o, r, l, d]

sorted() - 排序元素

sorted() 方法用于对流中的元素进行排序。可以使用自然排序或提供自定义比较器。

示例:自然排序
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2); List<Integer> sortedNumbers = numbers.stream() .sorted() .collect(Collectors.toList()); System.out.println(sortedNumbers); // 输出: [1, 2, 3, 5, 8]
示例:自定义排序
List<String> words = Arrays.asList("apple", "banana", "cherry", "date"); List<String> sortedByLength = words.stream() .sorted(Comparator.comparingInt(String::length)) .collect(Collectors.toList()); System.out.println(sortedByLength); // 输出: [date, apple, cherry, banana]

distinct() - 去重元素

distinct() 方法用于去除流中的重复元素,基于元素的 equals() 方法。

示例:去除重复数字
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4); List<Integer> uniqueNumbers = numbers.stream() .distinct() .collect(Collectors.toList()); System.out.println(uniqueNumbers); // 输出: [1, 2, 3, 4]
示例:去除重复字符串
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana"); List<String> uniqueWords = words.stream() .distinct() .collect(Collectors.toList()); System.out.println(uniqueWords); // 输出: [apple, banana, cherry]

limit() 和 skip() - 限制和跳过元素

limit() 方法返回一个不超过指定长度的流,skip() 方法跳过指定数量的元素。

示例:取前N个元素
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> firstThree = numbers.stream() .limit(3) .collect(Collectors.toList()); System.out.println(firstThree); // 输出: [1, 2, 3]
示例:跳过前N个元素
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> afterThree = numbers.stream() .skip(3) .collect(Collectors.toList()); System.out.println(afterThree); // 输出: [4, 5, 6, 7, 8, 9, 10]

终端操作

终端操作会触发流的执行,并返回一个结果或副作用。一旦执行了终端操作,流就会被消耗,不能再被使用。

forEach() - 遍历元素

forEach() 方法用于对每个元素执行指定的操作,是一种常见的终端操作。

示例:打印元素
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); names.stream() .forEach(System.out::println); // 输出: // Alice // Bob // Charlie // David
示例:计算平方和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = 0; numbers.stream() .map(n -> n * n) .forEach(n -> sum += n); System.out.println(sum); // 输出: 55

collect() - 收集元素

collect() 方法用于将流中的元素收集到一个集合或其他数据结构中。

示例:收集到List
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> upperCaseNames = names.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(upperCaseNames); // 输出: [ALICE, BOB, CHARLIE, DAVID]
示例:收集到Set
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry"); Set uniqueWords = words.stream() .collect(Collectors.toSet()); System.out.println(uniqueWords); // 输出: [apple, banana, cherry]

count() - 计数元素

count() 方法用于返回流中元素的数量。

示例:计算元素数量
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); long count = names.stream().count(); System.out.println(count); // 输出: 4
示例:计算满足条件的元素数量
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); long evenCount = numbers.stream() .filter(n -> n % 2 == 0) .count(); System.out.println(evenCount); // 输出: 5

reduce() - 归约元素

reduce() 方法用于将流中的元素合并为一个值,是一种强大的终端操作。

示例:计算总和
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, (a, b) -> a + b); System.out.println(sum); // 输出: 15
示例:连接字符串
List<String> words = Arrays.asList("Hello", " ", "World", "!"); String result = words.stream() .reduce("", String::concat); System.out.println(result); // 输出: Hello World!

匹配操作 - anyMatch(), allMatch(), noneMatch()

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

anyMatch()
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); boolean hasEven = numbers.stream() .anyMatch(n -> n % 2 == 0); System.out.println(hasEven); // 输出: true
allMatch()
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10); boolean allEven = numbers.stream() .allMatch(n -> n % 2 == 0); System.out.println(allEven); // 输出: true
noneMatch()
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9); boolean noneEven = numbers.stream() .noneMatch(n -> n % 2 == 0); System.out.println(noneEven); // 输出: true

综合示例

下面是一些使用Stream API的综合示例,展示了如何组合多种操作来解决实际问题。

员工数据处理

员工类定义

class Employee { private int id; private String name; private String department; private double salary; private int age; // 构造函数、getter和setter方法 public Employee(int id, String name, String department, double salary, int age) { this.id = id; this.name = name; this.department = department; this.salary = salary; this.age = age; } // Getter方法 public int getId() { return id; } public String getName() { return name; } public String getDepartment() { return department; } public double getSalary() { return salary; } public int getAge() { return age; } @Override public String toString() { return "Employee{id=" + id + ", name='" + name + "', department='" + department + "', salary=" + salary + ", age=" + age + "}"; } }

创建员工列表

List<Employee> employees = Arrays.asList( new Employee(1, "Alice", "HR", 5000, 25), new Employee(2, "Bob", "IT", 6000, 30), new Employee(3, "Charlie", "Finance", 5500, 35), new Employee(4, "David", "IT", 7000, 40), new Employee(5, "Eve", "HR", 4500, 22), new Employee(6, "Frank", "Finance", 6500, 45), new Employee(7, "Grace", "IT", 7500, 32), new Employee(8, "Heidi", "HR", 5200, 28) );

示例1:按部门分组

// 按部门分组 Map<String, List<Employee>> employeesByDepartment = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); // 打印每个部门的员工 employeesByDepartment.forEach((department, empList) -> { System.out.println("部门: " + department); empList.forEach(emp -> System.out.println(" " + emp.getName())); }); // 输出: // 部门: HR // Alice // Eve // Heidi // 部门: IT // Bob // David // Grace // 部门: Finance // Charlie // Frank

示例2:计算各部门平均工资

// 计算各部门平均工资 Map<String, Double> averageSalaryByDepartment = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.averagingDouble(Employee::getSalary) )); // 打印各部门平均工资 averageSalaryByDepartment.forEach((department, avgSalary) -> { System.out.println(department + ": " + String.format("%.2f", avgSalary)); }); // 输出: // HR: 4900.00 // IT: 6833.33 // Finance: 6000.00

示例3:找出每个部门最高薪的员工

// 找出每个部门最高薪的员工 Map<String, Optional<Employee>> highestPaidEmployeeByDepartment = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)) )); // 打印每个部门最高薪的员工 highestPaidEmployeeByDepartment.forEach((department, emp) -> { System.out.println(department + ": " + emp.map(Employee::getName).orElse("No employees")); }); // 输出: // HR: Heidi // IT: Grace // Finance: Frank

示例4:统计各部门员工数量

// 统计各部门员工数量 Map<String, Long> employeeCountByDepartment = employees.stream() .collect(Collectors.groupingBy( Employee::getDepartment, Collectors.counting() )); // 打印各部门员工数量 employeeCountByDepartment.forEach((department, count) -> { System.out.println(department + ": " + count); }); // 输出: // HR: 3 // IT: 3 // Finance: 2

示例1:统计单词频率

String text = "Hello world hello java world java programming"; Map<String, Long> wordFrequency = Arrays.stream(text.split("\\s+")) .collect(Collectors.groupingBy( String::toLowerCase, Collectors.counting() )); wordFrequency.forEach((word, count) -> { System.out.println(word + ": " + count); }); // 输出: // hello: 2 // world: 2 // java: 2 // programming: 1

示例2:并行流处理

List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000; i++) { numbers.add(i); } // 串行流计算平方和 long startTime = System.currentTimeMillis(); long sumSequential = numbers.stream() .mapToLong(n -> n * n) .sum(); long endTime = System.currentTimeMillis(); System.out.println("串行流结果: " + sumSequential + ", 耗时: " + (endTime - startTime) + "ms"); // 并行流计算平方和 startTime = System.currentTimeMillis(); long sumParallel = numbers.parallelStream() .mapToLong(n -> n * n) .sum(); endTime = System.currentTimeMillis(); System.out.println("并行流结果: " + sumParallel + ", 耗时: " + (endTime - startTime) + "ms"); // 输出: // 串行流结果: 333833500, 耗时: 2ms // 并行流结果: 333833500, 耗时: 1ms

示例3:分组并求和

class Order { private int id; private String customer; private double amount; public Order(int id, String customer, double amount) { this.id = id; this.customer = customer; this.amount = amount; } public String getCustomer() { return customer; } public double getAmount() { return amount; } } List<Order> orders = Arrays.asList( new Order(1, "Alice", 100), new Order(2, "Bob", 200), new Order(3, "Alice", 150), new Order(4, "Charlie", 300), new Order(5, "Bob", 250) ); // 计算每个客户的订单总金额 Map<String, Double> totalAmountByCustomer = orders.stream() .collect(Collectors.groupingBy( Order::getCustomer, Collectors.summingDouble(Order::getAmount) )); totalAmountByCustomer.forEach((customer, total) -> { System.out.println(customer + ": " + total); }); // 输出: // Alice: 250.0 // Bob: 450.0 // Charlie: 300.0

示例4:多级分组

class Product { private String name; private String category; private String subCategory; private double price; public Product(String name, String category, String subCategory, double price) { this.name = name; this.category = category; this.subCategory = subCategory; this.price = price; } public String getCategory() { return category; } public String getSubCategory() { return subCategory; } public double getPrice() { return price; } } List<Product> products = Arrays.asList( new Product("iPhone", "Electronics", "Phones", 999), new Product("MacBook", "Electronics", "Computers", 1999), new Product("iPad", "Electronics", "Tablets", 799), new Product("T-Shirt", "Clothing", "Men", 29), new Product("Dress", "Clothing", "Women", 79), new Product("Jeans", "Clothing", "Men", 59) ); // 按类别和子类别分组 Map<String, Map<String, List<Product>>> productsByCategory = products.stream() .collect(Collectors.groupingBy( Product::getCategory, Collectors.groupingBy(Product::getSubCategory) )); // 打印分组结果 productsByCategory.forEach((category, subCategories) -> { System.out.println("类别: " + category); subCategories.forEach((subCategory, productList) -> { System.out.println(" 子类别: " + subCategory); productList.forEach(product -> System.out.println(" " + product.getPrice())); }); }); // 输出: // 类别: Electronics // 子类别: Phones // 999.0 // 子类别: Computers // 1999.0 // 子类别: Tablets // 799.0 // 类别: Clothing // 子类别: Men // 29.0 // 59.0 // 子类别: Women // 子类别: Women // 79.0

性能考量

Stream 性能优化

  • 优先使用原始类型流

    使用 IntStream、LongStream 和 DoubleStream 代替 Stream<Integer> 等包装类型,可以减少自动装箱和拆箱的开销。

  • 合理使用并行流

    并行流适合计算密集型任务和大数据集,但对于小数据集或I/O密集型任务可能会导致性能下降。

  • 避免重复计算

    Stream 操作是惰性的,但多次调用终端操作会导致流被重新计算。如果需要多次使用结果,应考虑缓存。

  • 合理排序

    排序是一个昂贵的操作,特别是对于大数据集。如果可能,尽量避免不必要的排序。

  • 优化过滤器位置

    将过滤操作放在流处理的早期,可以减少后续操作需要处理的元素数量。

串行 vs 并行流性能比较

注意:实际性能会受多种因素影响,包括数据集大小、硬件配置和任务类型。

性能示例

原始类型流 vs 包装类型流

// 包装类型流 List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000000; i++) { numbers.add(i); } long startTime = System.currentTimeMillis(); long sumBoxed = numbers.stream() .mapToLong(Integer::longValue) .sum(); long endTime = System.currentTimeMillis(); System.out.println("包装类型流耗时: " + (endTime - startTime) + "ms"); // 原始类型流 IntStream intStream = IntStream.rangeClosed(1, 1000000); startTime = System.currentTimeMillis(); long sumPrimitive = intStream.sum(); endTime = System.currentTimeMillis(); System.out.println("原始类型流耗时: " + (endTime - startTime) + "ms"); // 输出示例: // 包装类型流耗时: 10ms // 原始类型流耗时: 1ms

串行流 vs 并行流

List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000000; i++) { numbers.add(i); } // 串行流 long startTime = System.currentTimeMillis(); long sumSequential = numbers.stream() .mapToLong(n -> n * n) .sum(); long endTime = System.currentTimeMillis(); System.out.println("串行流耗时: " + (endTime - startTime) + "ms"); // 并行流 startTime = System.currentTimeMillis(); long sumParallel = numbers.parallelStream() .mapToLong(n -> n * n) .sum(); endTime = System.currentTimeMillis(); System.out.println("并行流耗时: " + (endTime - startTime) + "ms"); // 输出示例: // 串行流耗时: 15ms // 并行流耗时: 8ms

学习资源

以下是一些有用的学习资源,可以帮助你更深入地了解Java 8 Stream API。

官方文档

Java 8 Stream API的官方文档是学习的最佳起点,包含了完整的API参考和详细的使用说明。

访问官方文档

推荐书籍

《Java 8实战》是一本全面介绍Java 8新特性的书籍,其中包含了大量Stream API的示例和最佳实践。

查看书籍

在线教程

Baeldung、Oracle官方教程等网站提供了丰富的Java 8 Stream API教程和示例代码。

访问在线教程

视频教程

YouTube上有许多关于Java 8 Stream API的视频教程,适合喜欢视觉学习的开发者。

观看视频教程

练习题

HackerRank、LeetCode等平台提供了大量使用Stream API的练习题,可以帮助你巩固所学知识。

尝试练习题

Stack Overflow

Stack Overflow上有许多关于Java 8 Stream API的问题和解答,可以帮助你解决实际开发中遇到的问题。

浏览问题

小测验

测试你对Java 8 Stream API的理解。完成以下问题,检验你的学习成果。

1. 以下哪个是中间操作?

2. 如何创建一个并行流?

3. 以下代码的输出是什么?

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Optional result = numbers.stream() .filter(n -> n > 10) .findFirst(); System.out.println(result.orElse(-1));

4. 哪个方法用于将流中的元素合并为一个值?

5. 以下哪个不是Stream API的特点?