Java Stream 技术详解
简介
在 Java 8 中引入的 Stream API 是一个强大的功能,它提供了一种高效且富有表现力的方式来处理集合数据。Stream 允许你以声明式的方式处理数据,就像 SQL 查询一样,让代码更简洁、易读,同时还能充分利用多核处理器的优势进行并行处理。本文将详细介绍 Java Stream 的基础概念、使用方法、常见实践以及最佳实践,帮助你深入理解并高效使用 Java Stream。
目录
- 基础概念
- 使用方法
- 创建 Stream
- 中间操作
- 终端操作
- 常见实践
- 过滤和映射
- 排序
- 聚合操作
- 最佳实践
- 避免重复创建 Stream
- 合理使用并行流
- 保持代码的可读性
- 小结
- 参考资料
基础概念
什么是 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 可以让你的代码更简洁、易读,同时提高性能。