欢迎光临
我们一直在努力

JDK 9 Stream新特性解析:从“能用”到“好用”的转变

一、前言

在前面篇文章中,我们系统学习了 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 () 核心区别

    特性

    takeWhile()

    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() 方法,如何进一步简化收集操作。

    赞(0)
    未经允许不得转载:171主机测评 » JDK 9 Stream新特性解析:从“能用”到“好用”的转变
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址