跳转至

Java Streams 技术全面解析

简介

在 Java 8 及之后的版本中,引入了 Streams API,这是一个强大且高效的工具,用于处理集合数据。Streams API 提供了一种声明式的方式来处理数据集合,使得代码更加简洁、易读且易于维护。它允许开发者以一种类似于 SQL 查询的方式对集合进行过滤、映射、排序等操作,大大提高了数据处理的效率。本文将详细介绍 Java Streams 的基础概念、使用方法、常见实践以及最佳实践。

目录

  1. 基础概念
  2. 使用方法
  3. 常见实践
  4. 最佳实践
  5. 小结
  6. 参考资料

1. 基础概念

什么是 Stream

Stream 是 Java 8 引入的一个新的抽象概念,它代表了一个元素序列,并支持各种聚合操作。Stream 不是一个数据结构,它不会存储数据,而是对数据进行处理。Stream 可以来自于集合、数组、I/O 通道等。

流的特性

  • 管道化操作:Stream 操作可以链式调用,形成一个管道,每个操作都是一个中间操作,最后一个操作是终端操作。
  • 延迟执行:中间操作不会立即执行,只有在终端操作被调用时才会执行。
  • 一次性使用:Stream 只能被消费一次,消费后就不能再使用。

流的操作类型

  • 中间操作:返回一个新的 Stream,例如 filtermapsorted 等。
  • 终端操作:产生一个最终结果,例如 forEachcollectcount 等。

2. 使用方法

创建 Stream

可以从集合、数组等创建 Stream。以下是一些常见的创建方式:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 从集合创建 Stream
        List<String> list = Arrays.asList("apple", "banana", "cherry");
        Stream<String> streamFromList = list.stream();

        // 从数组创建 Stream
        String[] array = {"apple", "banana", "cherry"};
        Stream<String> streamFromArray = Arrays.stream(array);

        // 创建空 Stream
        Stream<String> emptyStream = Stream.empty();

        // 创建无限 Stream
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
    }
}

中间操作

中间操作可以对 Stream 中的元素进行过滤、映射、排序等操作。以下是一些常见的中间操作示例:

import java.util.Arrays;
import java.util.List;

public class IntermediateOperationsExample {
    public static void main(String[] args) {
        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)
                .toList();
        System.out.println("Even numbers: " + evenNumbers);

        // 映射操作
        List<Integer> squaredNumbers = numbers.stream()
                .map(n -> n * n)
                .toList();
        System.out.println("Squared numbers: " + squaredNumbers);

        // 排序操作
        List<Integer> sortedNumbers = numbers.stream()
                .sorted()
                .toList();
        System.out.println("Sorted numbers: " + sortedNumbers);
    }
}

终端操作

终端操作会触发 Stream 的执行,并产生一个最终结果。以下是一些常见的终端操作示例:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class TerminalOperationsExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // forEach 操作
        numbers.stream()
                .forEach(System.out::println);

        // count 操作
        long count = numbers.stream()
                .count();
        System.out.println("Count: " + count);

        // reduce 操作
        Optional<Integer> sum = numbers.stream()
                .reduce((a, b) -> a + b);
        System.out.println("Sum: " + sum.orElse(0));

        // collect 操作
        List<Integer> collectedList = numbers.stream()
                .collect(java.util.stream.Collectors.toList());
        System.out.println("Collected list: " + collectedList);
    }
}

3. 常见实践

过滤和映射

过滤和映射是最常见的 Stream 操作之一,用于筛选出符合条件的元素并进行转换。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FilterAndMapExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date");

        // 过滤长度大于 5 的单词,并转换为大写
        List<String> filteredAndMappedWords = words.stream()
                .filter(word -> word.length() > 5)
                .map(String::toUpperCase)
                .collect(Collectors.toList());

        System.out.println("Filtered and mapped words: " + filteredAndMappedWords);
    }
}

分组和分区

分组和分区可以将 Stream 中的元素按照某个条件进行分组或分区。

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingAndPartitioningExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 分组操作
        Map<Integer, List<Integer>> groupedByRemainder = numbers.stream()
                .collect(Collectors.groupingBy(n -> n % 3));
        System.out.println("Grouped by remainder: " + groupedByRemainder);

        // 分区操作
        Map<Boolean, List<Integer>> partitionedByEven = numbers.stream()
                .collect(Collectors.partitioningBy(n -> n % 2 == 0));
        System.out.println("Partitioned by even: " + partitionedByEven);
    }
}

4. 最佳实践

避免不必要的中间操作

中间操作是延迟执行的,但过多的中间操作会增加代码的复杂度和执行时间。尽量减少不必要的中间操作。

使用并行流时要谨慎

并行流可以提高处理大数据集的效率,但并行流的使用也会带来额外的开销,例如线程创建和同步。在使用并行流时,要确保数据集足够大,并且操作是线程安全的。

import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用并行流计算总和
        long sum = numbers.parallelStream()
                .mapToLong(Integer::longValue)
                .sum();
        System.out.println("Sum using parallel stream: " + sum);
    }
}

及时关闭流

虽然大多数 Stream 操作不需要手动关闭,但如果 Stream 是从 I/O 通道创建的,需要及时关闭以释放资源。

5. 小结

Java Streams API 是一个强大且灵活的工具,它提供了一种声明式的方式来处理集合数据。通过使用 Stream,我们可以编写更加简洁、易读和易于维护的代码。在使用 Stream 时,要理解流的基础概念、掌握常见的操作方法,并遵循最佳实践,以提高代码的性能和可靠性。

6. 参考资料

  • 《Effective Java》(第 3 版)
  • 《Java 8 in Action》