跳转至

Java Stream 技术详解

简介

在 Java 8 中引入的 Stream API 是一个强大的功能,它提供了一种高效且富有表现力的方式来处理集合数据。Stream 允许你以声明式的方式处理数据,就像 SQL 查询一样,让代码更简洁、易读,同时还能充分利用多核处理器的优势进行并行处理。本文将详细介绍 Java Stream 的基础概念、使用方法、常见实践以及最佳实践,帮助你深入理解并高效使用 Java Stream。

目录

  1. 基础概念
  2. 使用方法
    • 创建 Stream
    • 中间操作
    • 终端操作
  3. 常见实践
    • 过滤和映射
    • 排序
    • 聚合操作
  4. 最佳实践
    • 避免重复创建 Stream
    • 合理使用并行流
    • 保持代码的可读性
  5. 小结
  6. 参考资料

基础概念

什么是 Stream

Stream 是 Java 8 引入的一个新抽象概念,它代表了一个元素序列,并支持各种聚合操作。Stream 不是一个数据结构,它不会存储数据,而是对数据源(如集合、数组等)进行一系列的操作。Stream 操作可以是中间操作(如过滤、映射)或终端操作(如收集、计数)。

特点

  • 声明式编程:使用 Stream 可以以声明式的方式表达数据处理逻辑,而不是像传统的迭代方式那样编写繁琐的循环代码。
  • 惰性求值:中间操作不会立即执行,只有在调用终端操作时才会触发计算。这可以提高性能,避免不必要的计算。
  • 并行处理:Stream 可以轻松地进行并行处理,充分利用多核处理器的优势。

使用方法

创建 Stream

Java 提供了多种方式来创建 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,允许你进行链式调用。常见的中间操作有: - filter:过滤满足条件的元素 - map:将元素进行转换 - distinct:去除重复元素 - sorted:对元素进行排序

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();

        // 将元素乘以 2
        List<Integer> multipliedNumbers = numbers.stream()
                                                 .map(n -> n * 2)
                                                 .toList();

        // 去除重复元素
        List<Integer> distinctNumbers = Arrays.asList(1, 2, 2, 3, 3, 4).stream()
                                                                       .distinct()
                                                                       .toList();

        // 对元素进行排序
        List<Integer> sortedNumbers = numbers.stream()
                                             .sorted()
                                             .toList();
    }
}

终端操作

终端操作会触发 Stream 的计算,并返回一个结果或副作用。常见的终端操作有: - forEach:对每个元素执行操作 - collect:将元素收集到一个集合中 - count:统计元素的数量 - reduce:将元素进行聚合

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

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

        // 对每个元素执行操作
        numbers.stream().forEach(System.out::println);

        // 将元素收集到一个集合中
        List<Integer> collectedNumbers = numbers.stream().collect(Collectors.toList());

        // 统计元素的数量
        long count = numbers.stream().count();

        // 将元素进行聚合
        int sum = numbers.stream().reduce(0, Integer::sum);
    }
}

常见实践

过滤和映射

过滤和映射是 Stream 中最常用的操作之一。例如,从一个包含字符串的列表中过滤出长度大于 3 的字符串,并将它们转换为大写:

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", "cat", "banana", "dog");

        List<String> result = words.stream()
                                   .filter(word -> word.length() > 3)
                                   .map(String::toUpperCase)
                                   .collect(Collectors.toList());

        System.out.println(result);
    }
}

排序

对元素进行排序也是常见的需求。例如,对一个包含整数的列表进行降序排序:

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

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

        List<Integer> sortedNumbers = numbers.stream()
                                             .sorted((a, b) -> b - a)
                                             .collect(Collectors.toList());

        System.out.println(sortedNumbers);
    }
}

聚合操作

聚合操作可以将元素进行合并或计算。例如,计算一个包含整数的列表的总和、平均值和最大值:

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

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

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

        System.out.println("Sum: " + stats.getSum());
        System.out.println("Average: " + stats.getAverage());
        System.out.println("Max: " + stats.getMax());
    }
}

最佳实践

避免重复创建 Stream

在使用 Stream 时,尽量避免重复创建 Stream。因为每次创建 Stream 都会有一定的开销。例如:

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

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

        // 不好的做法
        long count = numbers.stream().count();
        int sum = numbers.stream().reduce(0, Integer::sum);

        // 好的做法
        var stream = numbers.stream();
        long count2 = stream.count();
        int sum2 = stream.reduce(0, Integer::sum); // 错误,Stream 已被使用
    }
}

合理使用并行流

并行流可以提高处理大量数据的性能,但也会带来一些额外的开销。在使用并行流时,需要考虑数据量和处理复杂度。例如:

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);

        // 使用并行流
        long sum = numbers.parallelStream()
                          .mapToInt(Integer::intValue)
                          .sum();
    }
}

保持代码的可读性

虽然 Stream 可以让代码更简洁,但也要注意保持代码的可读性。避免使用过于复杂的链式调用,适当添加注释。

小结

Java Stream 是一个强大的工具,它提供了一种高效且富有表现力的方式来处理集合数据。通过本文的介绍,你应该对 Java Stream 的基础概念、使用方法、常见实践和最佳实践有了更深入的理解。在实际开发中,合理使用 Stream 可以让你的代码更简洁、易读,同时提高性能。

参考资料