Java Stream API 核心概念与简介

Java Stream API是Java 8中引入的一个革命性特性,它允许开发者以声明式方式处理数据集合(如List、Set、Map等)。它并非一种新的数据结构,而是用于对集合数据进行高效计算(特别是聚合操作)的强大工具。其核心思想是让开发者专注于“做什么”而非“怎么做”,通过内部迭代(Internal Iteration)和延迟执行(Lazy Evaluation)等机制,极大地简化了代码并提升了执行效率。

流的创建

要使用Stream,首先需要创建一个流。Stream可以从多种数据源创建,最常见的是集合。

从集合创建

所有继承自`Collection`接口的类(如List、Set)都提供了一个`stream()`方法。

List list = Arrays.asList(a, b, c); Stream stream = list.stream();

从数组创建

可以使用`Arrays.stream()`静态方法。

int[] numbers = {1, 2, 3, 4, 5}; IntStream stream = Arrays.stream(numbers);

使用Stream.of()

对于离散的值,可以使用`Stream.of()`方法直接创建。

Stream stream = Stream.of(Java, Python, Go);

生成无限流

Stream API提供了`generate()`和`iterate()`方法来创建无限流,常与`limit()`结合使用。

// 生成10个随机数Stream randomStream = Stream.generate(Math::random).limit(10);// 创建一个从0开始,每次加2的流,取前5个Stream evenStream = Stream.iterate(0, n -> n + 2).limit(5);

中间操作

中间操作(Intermediate Operations)是对流进行一系列处理并返回一个新流的操作。这些操作是惰性的,意味着它们不会立即执行,而是等到终端操作被调用时才会真正执行。

过滤(filter)

`filter`操作用于根据条件筛选元素,它接收一个Predicate函数式接口作为参数。

List words = Arrays.asList(Java, Stream, API, Lambda);List longWords = words.stream()                              .filter(word -> word.length() > 3)                              .collect(Collectors.toList());// 结果: [Java, Stream, Lambda]

映射(map与flatMap)

`map`操作用于将流中的每个元素通过给定的函数进行转换,生成一个新的元素。

List words = Arrays.asList(Hello, World);List wordLengths = words.stream()                                 .map(String::length)                                 .collect(Collectors.toList());// 结果: [5, 5]

`flatMap`用于将每个元素转换成一个流,然后将所有这些流连接成一个流。它在处理嵌套结构时非常有用。

List> listOfLists = Arrays.asList(  Arrays.asList(a, b),  Arrays.asList(c, d));List flatList = listOfLists.stream()                                   .flatMap(Collection::stream)                                   .collect(Collectors.toList());// 结果: [a, b, c, d]

去重(distinct)与排序(sorted)

`distinct`方法基于`equals()`方法返回流中唯一元素(去重)的流。

Stream.of(1, 2, 1, 3, 2, 4).distinct().forEach(System.out::print);// 结果: 1 2 3 4

`sorted`方法用于对流中的元素进行排序。它可以自然排序,也可以接收一个Comparator。

Stream.of(3, 1, 4, 2).sorted().forEach(System.out::print);// 自然排序结果: 1 2 3 4Stream.of(Java, C, Python)      .sorted((s1, s2) -> s1.length() - s2.length())      .forEach(System.out::print);// 按长度排序结果: C, Java, Python

终端操作

终端操作(Terminal Operations)会消费流,并产生一个最终结果或副作用。一旦执行了终端操作,流就被消耗了,无法再被使用。

遍历(forEach)

`forEach`是最常见的终端操作,它对流中的每个元素执行指定的操作。

Stream.of(a, b, c).forEach(System.out::println);

收集(collect)

`collect`是一个极其强大的还原操作,它使用一个Collector(收集器)将流中的元素累积成一个汇总结果,例如List、Set或Map。

List collectedList = Stream.of(a, b, c)                                   .collect(Collectors.toList());Set collectedSet = Stream.of(a, b, a)                                 .collect(Collectors.toSet());// Set结果: [a, b]// 收集为Map,键为元素本身,值为元素长度Map map = Stream.of(Java, Go)        .collect(Collectors.toMap(Function.identity(), String::length));// Map结果: {Java=4, Go=2}

匹配(anyMatch, allMatch, noneMatch)

这些操作用于检查流中的元素是否与给定的Predicate匹配,返回一个布尔值。

List numbers = Arrays.asList(1, 2, 3, 4, 5);boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // trueboolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // falseboolean noneNegative = numbers.stream().noneMatch(n -> n < 0); // true

聚合(reduce)

`reduce`操作通过一个二元操作符将流中的元素组合起来,产生一个单一的值。它是函数式编程中fold操作的具体实现。

// 求和的两种方式Optional sum1 = Stream.of(1, 2, 3, 4).reduce((a, b) -> a + b);Integer sum2 = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);// 结果都是 10

并行流与性能考量

Stream API的一个巨大优势是可以轻松实现并行处理。只需将`stream()`方法替换为`parallelStream()`,框架会自动将工作负载分配到多个CPU核心上。

long count = largeList.parallelStream()                      .filter(...)                      .count();

然而,并行并非银弹。它会带来额外的开销(如线程间通信、任务拆分与结果合并),只有在数据量足够大、每个元素的处理成本足够高时,性能提升才会明显。此外,确保操作是无状态的、不干涉的(non-interfering)且无关联的(stateless)是安全使用并行流的关键。

实践技巧与最佳实践

1. 优先使用方法引用:让代码更简洁、更易读。例如,使用`String::length`代替`s -> s.length()`。

2. 避免副作用:在流的操作(尤其是在`forEach`中)修改外部变量是一种不良实践,这会破坏函数的纯粹性并可能导致并发问题。应尽量使用无副作用的操作(如`collect`、`reduce`)来产生结果。

3. 谨慎使用并行流:如前所述,根据实际场景评估是否真的需要并行流。在IO密集型任务或数据结构拆分成本高(如LinkedList)的情况下,并行流可能适得其反。

4. 善用原始类型特化流:对于`int`, `long`, `double`等原始类型,使用`IntStream`, `LongStream`, `DoubleStream`可以避免不必要的装箱/拆箱开销,提升性能。

5. 理解操作的短路能力

某些中间操作(如`limit`、`findFirst`)和终端操作(如`anyMatch`)具有短路(Short-Circuiting)能力。这意味着它们不需要处理整个流就能得到结果,在处理无限流或非常大的流时至关重要。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐