第一部分:引言与基本操作
在现代的编程中,Java Stream API 已经成为处理集合数据的一种流行和强大的工具。它可以让我们以声明性的方式处理数据,简化代码并提高可读性。本文将为大家详细介绍 Java Stream API 的核心概念、功能以及实例操作。
1. 什么是 Stream API?
Stream API 是 Java 8 中引入的一种新的数据处理工具,允许我们对数据进行转换、过滤、排序等操作。Stream 不存储数据,它只是数据的一种“视图”,我们可以理解为它是数据的一个管道,数据可以流过这个管道并且在流经时进行一系列的处理。
2. 如何创建 Stream?
有多种方法可以创建 Stream,下面是几种常见的方法:
import java.util.stream.Stream;
public class CreateStreamDemo {
public static void main(String[] args) {
// 1. 使用 Stream.of 方法
Stream<String> stream1 = Stream.of("A", "B", "C");
// 2. 从集合创建
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream2 = list.stream();
// 3. 使用 Stream.iterate 方法
Stream<Integer> stream3 = Stream.iterate(0, n -> n + 2).limit(5); // 产生 0, 2, 4, 6, 8
// 4. 使用 Stream.generate 方法
Stream<Double> stream4 = Stream.generate(Math::random).limit(3);
}
}
3. 基本的 Stream 操作
Stream 提供了丰富的操作来处理数据。这些操作大致可以分为两类:中间操作和终止操作。中间操作返回一个新的 Stream,终止操作返回一个结果或一个副作用。
-
中间操作
filter(Predicate<T> predicate)
:过滤 Stream 中的元素。map(Function<T, R> mapper)
:转换 Stream 中的每个元素。sorted(Comparator<? super T> comparator)
:排序 Stream。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<String> nameStream = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.sorted();
nameStream.forEach(System.out::println); // 输出 ALICE
-
终止操作
forEach(Consumer<? super T> action)
:对 Stream 中的每个元素执行操作。collect(Collector<? super T,A,R> collector)
:将 Stream 转换为其他形式。sum()
,max(Comparator<? super T> comparator)
,min(Comparator<? super T> comparator)
:计算 Stream 中的元素。
这部分只是 Stream API 的冰山一角。为了更好地掌握这个工具,我们将在接下来的部分中深入探索它的高级功能。
第二部分:高级操作与常见使用场景
4. 高级 Stream 操作
除了上述的基本操作,Stream API 还提供了一系列的高级操作,以更复杂的方式处理数据。
-
flatMap(Function<? super T,? extends Stream<? extends R>> mapper):
flatMap
允许我们将每个元素转换为一个 Stream,然后将所有的 Stream 连接到一起。
List<String> words = Arrays.asList("Hello World", "Java Stream");
Stream<String> wordStream = words.stream()
.flatMap(phrase -> Stream.of(phrase.split(" ")))
.map(String::toLowerCase);
wordStream.forEach(System.out::println); // 输出 hello, world, java, stream
-
distinct():
此操作返回一个包含 Stream 中唯一元素的 Stream。
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
numbers.stream()
.distinct()
.forEach(System.out::println); // 输出 1, 2, 3, 4, 5
-
peek(Consumer<? super T> action):
此操作返回一个与原 Stream 相同的 Stream,但在返回之前,对每个元素执行给定的操作。
List<String> names = Arrays.asList("Alice", "Bob");
names.stream()
.peek(name -> System.out.println("Name before uppercase: " + name))
.map(String::toUpperCase)
.peek(name -> System.out.println("Name after uppercase: " + name))
.collect(Collectors.toList());
5. 收集 Stream 结果
经常,我们需要将 Stream 的结果收集到一个列表、集合或其他数据结构中。collect
方法和 Collectors
类使这变得非常简单。
- 收集到列表:
List<String> collectedNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
- 收集到集合:
Set<String> collectedSet = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toSet());
- 分组数据:
Map<Character, List<String>> groupedByNameInitial = names.stream()
.collect(Collectors.groupingBy(name -> name.charAt(0)));
6. 常见使用场景
- 数据过滤:
List<String> filteredNames = names.stream()
.filter(name -> name.length() > 3)
.collect(Collectors.toList());
- 数据转换:
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
- 数据统计:
Double averageNameLength = names.stream()
.collect(Collectors.averagingInt(String::length));
- 并发处理:
使用 parallelStream()
代替 stream()
可以在多个线程上并行处理数据。
List<String> parallelProcessedNames = names.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());
Stream API 的力量在于它的灵活性和表达力。你可以以几乎任何方式组合操作,以简洁、可读的方式处理数据。
第三部分:性能优化与真实世界应用
7. 性能优化
虽然 Stream API 提供了简洁和强大的数据处理方法,但不恰当的使用可能会导致性能问题。以下是几个关于如何优化 Stream 性能的建议:
-
限制数据大小:
使用
limit()
方法来限制 Stream 的大小,特别是当你使用无限 Stream(如Stream.iterate
和Stream.generate
)时。
Stream<Integer> infiniteNumbers = Stream.iterate(1, n -> n + 1);
List<Integer> firstTenNumbers = infiniteNumbers.limit(10).collect(Collectors.toList());
-
避免过度盒装:
使用原始类型流(如
IntStream
,LongStream
,DoubleStream
)可以避免不必要的对象装箱和拆箱,从而提高性能。
IntStream.rangeClosed(1, 5).forEach(System.out::println);
-
尽可能地使用并行流:
对于大量数据,使用
parallelStream()
可以利用多核处理器提高性能。但要注意线程安全问题和过多的线程开销。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
names.parallelStream().map(String::toUpperCase).forEach(System.out::println);
8. 真实世界应用
在实际开发中,Stream API 可以简化很多日常任务。以下是一些例子:
- 从数据库中过滤和转换数据:
假设你有一个用户列表,你想要找到所有年龄在 20 到 30 之间的用户,并将他们的名字转换为大写。
List<User> users = database.getAllUsers();
List<String> youngUserNames = users.stream()
.filter(user -> user.getAge() >= 20 && user.getAge() <= 30)
.map(User::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
- 处理日志文件:
想象一下,你有一个日志文件,你想要统计其中的错误消息。
List<String> lines = Files.readAllLines(Paths.get("log.txt"));
long errorCount = lines.stream()
.filter(line -> line.contains("[ERROR]"))
.count();
- 统计词频:
假设你要分析一篇文章中每个单词出现的次数。
String text = "Hello world. Hello Java. Java Stream API.";
Map<String, Long> wordFrequency = Arrays.stream(text.split("\\W+"))
.map(String::toLowerCase)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
结论:
Stream API 是 Java 8 引入的一个强大的工具,使得数据处理变得更加简单和直观。通过合理使用和考虑性能因素,你可以最大化其潜力,简化代码并提高代码效率。不论你是处理简单的数据列表还是复杂的数据流,Stream API 都可以成为你的得力助手。