Java的Stream流详细讲解

75 阅读9分钟

引言

在本章中,我们将介绍Java 8引入的Stream API,这是对Java集合操作的一项重大补充,提供了一种声明式的处理方式。

Java 8的流式处理概述

流式处理是一种数据处理方式,它允许以声明式的方式处理集合数据,而不需要编写复杂的循环和条件语句。

示例:传统的集合处理方式

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
    if (name.startsWith("A")) {
        System.out.println(name);
    }
}

这种方式虽然直观,但在处理复杂逻辑时可能会变得冗长和难以维护。

Stream API的重要性和优势

Stream API提供了一种更加简洁、易读且功能强大的集合处理方式,支持链式调用、并行处理等特性。

示例:使用Stream API处理集合

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
    .filter(name -> name.startsWith("A"))
    .forEach(System.out::println);

这段代码以声明式的方式完成了相同的功能,更加简洁和易于理解。

优势概述

  • 声明式风格:以声明的方式处理数据,提高代码可读性。
  • 函数式编程:支持Lambda表达式,简化代码编写。
  • 并行处理:易于实现数据的并行处理,提高性能。
  • 丰富的操作:提供了一系列丰富的操作,如过滤、映射、排序等。

Stream API基础

在本章中,我们将深入探讨Stream API的基本概念和特性,以及如何创建Stream对象。

Stream的概念和特性

Stream是Java 8中引入的一个新的抽象概念,它代表了一个元素序列,这些元素可以是集合中的元素,也可以是数组,甚至可以是生成器或I/O channel中的元素。

Stream的特点

  • 不可变性:Stream本身是不可变的,对Stream的操作不会修改原始数据源。
  • 延迟性:Stream操作是延迟执行的,只有当真正需要结果时才会执行。
  • 中间操作:可以对Stream进行多次中间操作,如filtermap等。
  • 终止操作:最终需要一个终止操作来触发实际的计算,如forEachcount等。

创建Stream的几种方式

有多种方式可以创建Stream对象,包括从集合、数组、文件等。

示例:从集合创建Stream

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();

示例:从数组创建Stream

String[] namesArray = {"Alice", "Bob", "Charlie"};
Stream<String> nameStreamFromArray = Arrays.stream(namesArray);

示例:创建无限的Stream

Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);

示例:创建由Lambda表达式生成的Stream

Stream<String> generatedStream = Stream.generate(() -> "Generated");

中间操作

中间操作是Stream API中的一类操作,它们将一个Stream转换成另一个Stream,并且可以链接起来形成一个流水线。这些操作是惰性的,不会立即执行,只有在终止操作时才会实际处理元素。

过滤(filter

过滤操作用于选择满足特定条件的元素。

示例:过滤操作

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<String> filteredNames = names.stream().filter(name -> name.startsWith("A"));
filteredNames.forEach(System.out::println); // 只打印以"A"开头的名字

映射(map)和扁平化(flatMap

映射操作用于将每个元素转换成另一种形式或数据类型。

示例:映射操作

List<String> words = Arrays.asList("hello", "world");
Stream<String> lengths = words.stream().map(String::length);
lengths.forEach(System.out::println); // 打印每个单词的长度

示例:扁平化操作

List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("a", "b"), 
    Arrays.asList("c", "d", "e")
);
Stream<String> flattenedStream = listOfLists.stream().flatMap(List::stream);
flattenedStream.forEach(System.out::println); // 扁平化后的流

排序(sorted

排序操作用于对Stream中的元素进行排序。

示例:排序操作

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
Stream<Integer> sortedNumbers = numbers.stream().sorted();
sortedNumbers.forEach(System.out::println); // 排序后的数字

去重(distinct

去重操作用于去除Stream中的重复元素。

示例:去重操作

List<String> items = Arrays.asList("apple", "banana", "apple", "orange");
Stream<String> uniqueItems = items.stream().distinct();
uniqueItems.forEach(System.out::println); // 去除重复项

终止操作

终止操作是Stream API中的一类操作,它们用于执行实际的计算并生成结果。一旦执行了终止操作,Stream流水线中的所有中间操作都会依次执行。

查找(findFirstfindAny

查找操作用于返回Stream中的一个元素,findFirst返回第一个元素,findAny返回任意一个元素。

示例:查找操作

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> firstName = names.stream().findFirst();
firstName.ifPresent(System.out::println); // 打印第一个元素

Optional<String> anyName = names.parallelStream().findAny();
anyName.ifPresent(System.out::println); // 打印任意元素

计数(count

计数操作用于返回Stream中的元素数量。

示例:计数操作

long uniqueCount = names.stream().distinct().count();
System.out.println("Unique count: " + uniqueCount);

规约(reduce

规约操作用于将Stream中的元素反复结合,最终得出一个结果。

示例:规约操作

int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("Sum: " + sum);

Optional<Integer> max = numbers.stream().reduce(Integer::max);
max.ifPresent(System.out::println); // 打印最大值

收集(collect

收集操作用于将Stream中的元素累积到一个结果容器中,例如列表或集合。

示例:收集操作

List<String> sortedNames = names.stream()
    .sorted()
    .collect(Collectors.toList());
System.out.println(sortedNames);

自定义收集器

Java 8允许使用Collector接口自定义收集操作。

Collector<String, ?, List<String>> collector = Collector.of(
    ArrayList::new, 
    List::add,
    (left, right) -> { left.addAll(right); return left; }
);

List<String> customCollected = names.stream().collect(collector);
System.out.println(customCollected);

并行流

并行流是Java 8 Stream API的一个重要特性,它允许利用多核处理器的计算能力来加速Stream的操作。

并行流的概念和优势

并行流背后的思想是将数据分成多个块,然后在不同的线程上并行处理这些块。

并行流的优势

  • 提高性能:通过并行处理,可以显著提高大数据集的处理速度。
  • 简化编程模型:开发者不需要编写多线程代码,就可以利用多线程的优势。

示例:创建并行流

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
long count = names.parallelStream().filter(name -> name.startsWith("A")).count();
System.out.println("Count: " + count);

如何将顺序流转换为并行流

任何Stream对象都可以通过调用parallelStream()方法来创建一个并行流。

示例:从顺序流到并行流

Stream<String> stream = names.stream(); // 顺序流
stream.parallel(); // 转换为并行流
stream.forEach(System.out::println);

并行流的注意事项

  • 小数据量:对于小数据量,使用并行流可能反而降低性能,因为线程创建和管理的开销可能大于其带来的性能提升。
  • 线程安全:并行流中的操作应该是线程安全的,以避免不可预测的结果。

示例:确保线程安全的操作

names.parallelStream().map(name -> {
    // 确保线程安全的逻辑
    return processThreadSafe(name);
}).forEach(System.out::println);

并行流与中间操作

并行流可以与中间操作一起使用,但是需要注意,某些中间操作可能会改变流的并行性。

示例:并行流中的过滤操作

long count = names.parallelStream()
    .filter(name -> name.startsWith("A")) // 并行执行过滤
    .count();

Stream API的高级特性

在本章中,我们将探讨Stream API中的一些高级特性,这些特性提供了更多的灵活性和强大的数据处理能力。

自定义收集器

Java 8允许开发者通过实现Collector接口来自定义收集操作。

示例:自定义收集器

Collector<Integer, ?, int[]> customCollector = Collector.of(
    ()-> new int[1],
    (arr, elem) -> { arr[0] += elem; return arr; },
    (left, right) -> { left[0] += right[0]; return left; }
);

int[] sumArray = numbers.stream().collect(customCollector);
System.out.println("Sum: " + sumArray[0]);

短路操作与非短路操作

某些Stream操作会根据结果提前终止处理,这些被称为短路操作。例如,anyMatch会立即返回结果,而不是处理整个Stream。

示例:短路操作

boolean anyStartsWithA = names.stream().anyMatch(name -> name.startsWith("A"));
System.out.println("Any start with A: " + anyStartsWithA);

状态和终端操作的结合

在某些情况下,可以将多个状态操作结合在一起,以减少对Stream的处理次数。

示例:结合状态和终端操作

Map<Boolean, List<String>> namesByLength = names.stream()
    .collect(Collectors.partitioningBy(name -> name.length() > 5));

namesByLength.forEach((k, v) -> System.out.println(k + " : " + v));

无限流的终止操作

无限流(如通过iterategenerate创建的流)需要使用特定的终止操作来避免无限循环。

示例:无限流的终止操作

Stream.iterate(0, i -> i + 1)
    .limit(10) // 使用limit作为终止操作
    .forEach(System.out::println);

并行流的排序

并行流的sorted操作与顺序流有所不同,因为它可能会导致不同的排序顺序。

示例:并行流的排序

List<String> sortedNames = names.parallelStream().sorted().collect(Collectors.toList());
System.out.println(sortedNames);

Stream API与Lambda表达式

Lambda表达式与Stream API紧密结合,提供了一种强大且表达性强的方式来处理数据集合。

Lambda表达式在Stream API中的应用

Lambda表达式允许开发者以一种简洁的方式实现Stream中的操作,而无需编写额外的方法。

示例:使用Lambda表达式进行过滤

names.stream()
    .filter(name -> name.length() > 4)
    .forEach(System.out::println);

方法引用和构造器引用

方法引用提供了一种快捷的方式,直接引用已有方法或构造器,作为Lambda表达式的参数。

示例:使用方法引用替代Lambda表达式

names.stream()
    .filter(String::startsWith)  // 等同于 name -> name.startsWith("A")
    .forEach(System.out::println);

示例:使用构造器引用创建对象流

Stream<String> stringStream = Stream.of("Java", "Lambda", "Stream");
Stream<MyObject> objectStream = stringStream.map(MyObject::new); // 假设MyObject有一个String参数的构造器
objectStream.forEach(obj -> System.out.println(obj.toString()));

高阶函数的支持

Stream API中的操作可以接收其他函数作为参数,这为使用Lambda表达式提供了广阔的空间。

示例:使用高阶函数

Function<String, Integer> stringLength = String::length;
int totalLength = names.stream()
                         .map(stringLength)
                         .reduce(0, Integer::sum);
System.out.println("Total length of all names: " + totalLength);

限制和性能考量

虽然Lambda表达式提供了便利,但在使用时也需要注意限制和性能问题。

示例:避免Lambda表达式中的复杂操作

// 避免在Lambda表达式中执行复杂或耗时的操作
names.stream()
    .filter(name -> complexAndExpensiveCheck(name))  // 可能影响性能
    .forEach(System.out::println);

实际案例分析

在本章中,我们将通过一些实际案例来展示Stream API在处理数据集合时的应用。

案例一:处理用户数据

假设我们有一个用户列表,我们需要提取用户的某些信息并进行排序。

示例:提取用户名并按长度排序

List<User> users = getUsers();
List<String> userNames = users.stream()
    .map(User::getName)
    .sorted(Comparator.comparingInt(String::length).reversed())
    .collect(Collectors.toList());

案例二:统计分析

对一组数据进行统计分析,如计算平均值、最大值和最小值。

示例:统计分析

int sum = numbers.stream().mapToInt(Integer::intValue).sum();
double average = (double) sum / numbers.size();
Optional<Integer> max = numbers.stream().max(Integer::compare);
Optional<Integer> min = numbers.stream().min(Integer::compare);

System.out.println("Average: " + average);
max.ifPresent(System.out::println);
min.ifPresent(System.out::println);

案例三:文本处理

使用Stream API进行文本处理,如单词计数。

示例:文本中的单词计数

String text = "Hello World, welcome to the world of Stream API!";
Map<String, Long> wordCount = Arrays.stream(text.split("\\s+"))
    .filter(word -> !word.isEmpty())
    .collect(Collectors.groupingByConcurrent(word -> word.toLowerCase(), Collectors.counting()));

wordCount.forEach((key, value) -> System.out.println(key + ": " + value));

案例四:复杂查询

使用Stream API实现复杂的查询逻辑,如根据多个条件过滤数据。

示例:复杂查询

List<Item> items = getItems();
List<Item> filteredItems = items.stream()
    .filter(item -> item.getPrice() > 100 && item.getCategory().equals("Electronics"))
    .sorted(Comparator.comparing(Item::getName))
    .collect(Collectors.toList());