一、前言
在前面篇文章中,我们系统学习了 JDK 8 Stream 的核心用法,也明确了它的诸多局限性:比如 Stream.iterate() 只能创建无限流、处理有序集合的条件截取需要写复杂逻辑、空值处理容易抛出 NPE 等。
JDK 9 作为 Stream 发布后的首个增强版本(2017 年发布),精准命中了 JDK 8 的这些痛点 —— 新增了 takeWhile() 、 dropWhile() 、 ofNullable() 等方法,还优化了 Stream.iterate() ,让 Stream 从 “能用” 变得 “更好用”。本文会逐一拆解这些新特性,通过 “JDK 8 写法 vs JDK 9 写法” 的对比,让你直观感受到版本升级带来的便捷性。
二、JDK 9 增强的核心背景
JDK 8 Stream 作为 “奠基版本”,解决了 “从无到有” 的问题,但在实际开发中暴露了几个高频痛点:
处理有序集合的 “前缀筛选 / 丢弃” 时, filter() 会遍历全部元素,效率低;
Stream.iterate()只能生成无限流,必须手动 limit() ,灵活性差;
处理可能为 null 的单个元素时,需要额外判空,代码冗余;
Stream 关闭逻辑不完善,无法适配资源型数据源(如 IO 流)。
JDK 9 的 Stream 增强,正是针对这些痛点的 “精准修复”—— 没有引入复杂概念,全部是 “小而美” 的实用优化。
三、Stream.iterate () 支持终止条件
这是 JDK 9 对 iterate() 最核心的优化,解决了 JDK 8 只能创建无限流的痛点。
1、JDK 8 的痛点
JDK 8 中 Stream.iterate() 只有一个重载方法:
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
只能生成无限流,必须配合 limit() 才能限制长度,且 limit() 的参数是 “硬编码的元素个数”,无法根据元素内容动态终止:
// JDK 8 写法:生成 1-5 的整数(必须硬编码 limit(5))
import java.util.stream.Stream;
public class IterateJdk8Demo {
public static void main(String[] args) {
Stream.iterate(1, n -> n + 1)
.limit(5) // 硬编码长度,无法根据条件(如 n<6)终止
.forEach(System.out::println); // 输出 1,2,3,4,5
}
}
如果需求是 “生成小于 10 的奇数”,JDK 8 只能先生成无限流,再 filter + limit ,逻辑冗余且效率低:
// JDK 8 生成小于 10 的奇数(冗余写法)
Stream.iterate(1, n -> n + 2)
.filter(n -> n < 10)
.limit(5) // 必须计算好个数,否则无限循环
.forEach(System.out::println);
2、JDK 9 的优化:新增带终止条件的重载方法
JDK 9 为 iterate() 新增了重载方法,支持传入终止条件( Predicate ):
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
-
seed:初始值;
-
hasNext:终止条件(返回 false 时停止生成);
-
next:元素生成规则。
3、实战对比:生成小于 10 的奇数
// JDK 9 写法:无需 limit,直接通过条件终止
import java.util.stream.Stream;
public class IterateJdk9Demo {
public static void main(String[] args) {
Stream.iterate(1, n -> n < 10, n -> n + 2)
.forEach(System.out::println); // 输出 1,3,5,7,9
}
}
4、核心优势
无需硬编码 limit() ,直接通过业务条件终止,代码更贴合需求;
避免生成多余元素,性能更高(JDK 8 会先生成无限流,再过滤,JDK 9 满足条件即停止);
支持基本类型流( IntStream / LongStream ),同样新增了带终止条件的重载方法:
// JDK 9 IntStream.iterate() 示例
import java.util.stream.IntStream;
public class IntStreamIterateDemo {
public static void main(String[] args) {
IntStream.iterate(1, n -> n < 10, n -> n + 2)
.forEach(System.out::println);
}
}
四、takeWhile () & dropWhile ()
这是 JDK 9 最实用的新增方法,解决了 JDK 8 处理有序集合 “前缀筛选 / 丢弃” 效率低的痛点。
1、先明确核心前提
takeWhile() 和 dropWhile() 仅对有序流 (如 List、有序 Set、IntStream 等)有意义,对于无序流(如 HashSet 生成的 Stream),行为等同于 filter() 。
2、takeWhile ():按条件截取前缀(遇到第一个不满足则停止)
2.1 作用
从流的开头开始,依次判断元素是否满足条件,遇到第一个不满足的元素立即停止,返回前面满足条件的元素组成的流。
2.2 JDK 8 vs JDK 9 对比
需求:从有序列表 [1,2,3,4,5,6] 中截取所有小于 4 的元素。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TakeWhileDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
// JDK 8 写法:filter 遍历全部元素(即使后面的元素都不满足)
List<Integer> jdk8Result = list.stream()
.filter(n -> n < 4)
.collect(Collectors.toList());
System.out.println("JDK 8 结果:" + jdk8Result); // [1,2,3]
// JDK 9 写法:takeWhile 遇到 4 立即停止,无需遍历后续元素
List<Integer> jdk9Result = list.stream()
.takeWhile(n -> n < 4)
.collect(Collectors.toList());
System.out.println("JDK 9 结果:" + jdk9Result); // [1,2,3]
}
}
2.3 性能差异
-
JDK 8 filter() :遍历全部 6 个元素;
-
JDK 9 takeWhile() :遍历到第 4 个元素(值为 4)时停止,仅遍历 4 个元素。数据量越大, takeWhile() 的性能优势越明显。
3、dropWhile ():按条件丢弃前缀(遇到第一个不满足则保留剩余)
3.1 作用
从流的开头开始,依次判断元素是否满足条件,遇到第一个不满足的元素立即停止,返回剩余所有元素组成的流(包括这个不满足的元素)。
3.2 JDK 8 vs JDK 9 对比
需求:从有序列表 [1,2,3,4,5,6] 中丢弃所有小于 4 的元素,保留剩余元素。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DropWhileDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
// JDK 8 写法:filter 遍历全部元素
List<Integer> jdk8Result = list.stream()
.filter(n -> n >= 4)
.collect(Collectors.toList());
System.out.println("JDK 8 结果:" + jdk8Result); // [4,5,6]
// JDK 9 写法:dropWhile 遇到 4 立即停止,保留剩余元素
List<Integer> jdk9Result = list.stream()
.dropWhile(n -> n < 4)
.collect(Collectors.toList());
System.out.println("JDK 9 结果:" + jdk9Result); // [4,5,6]
}
}
3.3 核心场景
-
处理分页数据:如 “丢弃前 10 页数据,保留后续数据”;
-
处理有序日志:如 “丢弃今天 0 点前的日志,保留之后的日志”。
4、takeWhile () vs filter () 核心区别
|
遇到第一个不满足条件即停止 |
遍历全部元素 |
|
高(提前终止) |
低(全量遍历) |
|
等同于 filter () |
正常过滤 |
|
有序流的前缀筛选 / 丢弃 |
任意流的全局筛选 |
Stre)
五、Stream.ofNullable ()
解决了 JDK 8 中处理 “可能为 null 的单个元素” 时需要额外判空的痛点。
1、JDK 8 的痛点
JDK 8 中 Stream.of() 不支持 null 元素(单个 null 会抛 NPE),处理可能为 null 的元素时需要手动判空:
// JDK 8 处理可能为 null 的元素(冗余写法)
import java.util.stream.Stream;
public class OfNullableJdk8Demo {
public static void main(String[] args) {
String str = null; // 可能为 null 的元素
Stream<String> stream;
// 必须手动判空,否则 Stream.of(null) 抛 NPE
if (str == null) {
stream = Stream.empty();
} else {
stream = Stream.of(str);
}
System.out.println(stream.count()); // 输出 0
}
}
2、JDK 9 的优化:Stream.ofNullable ()
JDK 9 新增 Stream.ofNullable(T t) 方法:
-
如果传入 null,返回空 Stream;
-
如果传入非 null,返回包含该元素的 Stream。
3、实战对比
// JDK 9 写法:一行代码处理 null,无需手动判空
import java.util.stream.Stream;
public class OfNullableJdk9Demo {
public static void main(String[] args) {
String str = null;
Stream<String> stream = Stream.ofNullable(str);
System.out.println(stream.count()); // 输出 0
String str2 = "Java";
Stream<String> stream2 = Stream.ofNullable(str2);
System.out.println(stream2.count()); // 输出 1
}
}
4、核心价值
-
简化空值处理逻辑,避免 NPE;
-
结合 flatMap 处理嵌套 null 场景更优雅:
// 处理可能为 null 的集合元素
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class OfNullableFlatMapDemo {
static class User {
private List<String> tags; // 可能为 null
public List<String> getTags() { return tags; }
}
public static void main(String[] args) {
List<User> userList = Arrays.asList(
new User() {{ setTags(Arrays.asList("Java", "Stream")); }},
new User() {{ setTags(null); }} // tags 为 null
);
// JDK 9 写法:flatMap + ofNullable 处理 null 集合
List<String> allTags = userList.stream()
.flatMap(user -> Stream.ofNullable(user.getTags()))
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(allTags); // 输出 [Java, Stream]
}
}
六、实现 AutoCloseable 接口
JDK 9 让 Stream 接口继承了 AutoCloseable 接口,意味着 Stream 可以配合 try-with-resources 语法自动关闭,适配资源型数据源(如 IO 流生成的 Stream)。
1、JDK 8 的痛点
JDK 8 中 Stream 未实现 AutoCloseable ,处理 IO 流生成的 Stream 时需要手动关闭:
// JDK 8 处理文件流(手动关闭)
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class CloseStreamJdk8Demo {
public static void main(String[] args) {
Stream<String> lines = null;
try {
lines = Files.lines(Paths.get("test.txt"));
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (lines != null) {
try {
lines.close(); // 手动关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2、JDK 9 的优化:自动关闭
// JDK 9 写法:try-with-resources 自动关闭 Stream
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class CloseStreamJdk9Demo {
public static void main(String[] args) {
try (Stream<String> lines = Files.lines(Paths.get("test.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意 :普通集合生成的 Stream 无需关闭,此特性主要针对 “占用系统资源的 Stream”(如 IO 流、网络流)。
七、JDK8 vs JDK9核心差异
|
特性 / 方法 |
JDK 8 状态 |
JDK 9 优化点 |
核心收益 |
|
Stream.iterate() |
仅支持无限流,需手动 limit |
新增终止条件,支持有限流 |
无需硬编码长度,更贴合业务 |
|
takeWhile() |
无,需用 filter 全量遍历 |
有序流前缀筛选,提前终止 |
性能提升,代码更简洁 |
|
dropWhile() |
无,需用 filter 全量遍历 |
有序流前缀筛选,提前终止 |
性能提升,代码更简洁 |
|
ofNullable() |
无,需手动判空 |
支持 null 元素,返回空 Stream |
避免 NPE,简化空值处理 |
|
关闭机制 |
未实现 AutoCloseable,需手动关闭 |
实现 AutoCloseable,支持自动关闭 |
简化资源型 Stream 管理 |
实战案例
需求。
八、实战案例
需求:从有序用户列表中,提取年龄从 18 开始、小于 30 的用户姓名,处理过程中兼容 null 用户,最终自动关闭流。
1、JDK 8 实现(冗余版)
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FullDemoJdk8 {
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public static void main(String[] args) {
List<User> userList = Arrays.asList(
new User("Alice", 18),
new User("Bob", 25),
new User("Charlie", 30),
new User("David", 35),
null // 可能为 null 的用户
);
Stream<User> stream = null;
try {
stream = userList.stream()
.filter(user -> user != null) // 手动判空
.filter(user -> user.getAge() >= 18 && user.getAge() < 30) // 全量遍历
.limit(2); // 硬编码 limit
List<String> names = stream.map(User::getName).collect(Collectors.toList());
System.out.println(names); // [Alice, Bob]
} finally {
if (stream != null) {
stream.close(); // 手动关闭
}
}
}
}
2、JDK 9 实现(简洁版)
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FullDemoJdk9 {
// 复用上面的 User 类,此处省略
public static void main(String[] args) {
List<User> userList = Arrays.asList(
new User("Alice", 18),
new User("Bob", 25),
new User("Charlie", 30),
new User("David", 35),
null
);
try (Stream<User> stream = userList.stream()
.flatMap(user -> Stream.ofNullable(user)) // 自动处理 null
.takeWhile(user -> user.getAge() < 30)) { // 提前终止遍历
List<String> names = stream.map(User::getName).collect(Collectors.toList());
System.out.println(names); // [Alice, Bob]
}
}
}
3、核心简化点
用 ofNullable() 替代手动 null 判断;
用 takeWhile() 替代 filter() + limit() ,无需硬编码长度;
用 try-with-resources 替代手动关闭流。
九、总结
JDK 9 是 Stream 的首次重要增强,核心优化集中在 “解决 JDK 8 痛点”: iterate() 支持终止条件、 takeWhile() / dropWhile() 提升有序流处理效率、 ofNullable() 简化空值处理;
JDK 9 的增强均为 “增量式优化”,完全兼容 JDK 8 语法,升级成本极低;
这些新特性的核心价值是 “代码更简洁、性能更高、空安全性更好”,尤其在处理有序集合和空值场景时优势明显。
下一篇文章,我们会聚焦 JDK 10-11,看看这两个版本对 Stream 做了哪些 “小步优化”,尤其是 JDK 11 新增的 toList() 方法,如何进一步简化收集操作。




