JDK 9 新特性详解

📅 2026/7/14 20:20:16
JDK 9 新特性详解
JDK 9 新特性详解JDK 9(代号无官方名称,于2017年9月21日正式发布)是继 JDK 1.8 之后最重要的 Java 版本之一,也是 JDK 采用新的半年发布周期后的第一个 LTS 版本。它引入了模块化系统(Project Jigsaw)、JShell 交互式编程工具、集合工厂方法、Stream API 增强、全新的 HTTP Client API 等革命性特性,彻底改变了 Java 的生态架构和开发体验。目录模块化系统(Project Jigsaw / JPMS)JShell 交互式编程工具集合工厂方法(Collection Factory Methods)接口私有方法try-with-resources 增强Stream API 增强Optional 类增强HTTP Client API(全新)钻石操作符增强(Diamond Operator)@SafeVarargs 扩展进程 API 增强多版本 JAR 文件(Multi-Release JAR)统一 JVM 日志Compact Strings(紧凑字符串)VarHandle(变量句柄)反应式流(Reactive Streams / Flow API)Stack-Walking API其他重要特性JDK 9 特性总览表1. 模块化系统(Project Jigsaw / JPMS)1.1 概述模块化系统是 JDK 9 最重要的特性,也是 Project Jigsaw 的核心成果。它将整个 JDK 拆分为约 70 个模块(java.base、java.sql、java.desktop等),同时允许开发者将自己的应用也组织为模块。核心概念:概念说明module-info.java模块描述文件,定义模块名称、依赖和导出requires声明对其他模块的依赖exports声明对外暴露的包opens声明允许反射访问的包uses声明使用的服务接口provides声明提供的服务实现transitive传递依赖(依赖的依赖也对外可见)模块图示例:module com.example.app { requires java.sql; // 依赖 java.sql 模块 requires transitive java.logging; // 传递依赖 exports com.example.app.api; // 对外暴露 api 包 opens com.example.app.model to javax.persistence; // 允许反射 uses com.example.app.spi.Plugin; // 使用服务 provides com.example.app.spi.Plugin with com.example.app.impl.MyPlugin; }目录结构:src/ ├── module-info.java // 模块描述 └── com/ └── example/ └── app/ ├── api/ │ └── UserService.java // 对外暴露 └── internal/ └── UserDao.java // 不暴露,外部不可访问模块化的好处:封装性:未exports的包对外不可见(强封装)可靠性:明确声明依赖关系,避免类路径冲突(JAR Hell)可维护性:模块边界清晰,便于理解和维护可扩展性:支持服务发现和加载(uses/provides)可裁剪性:自定义 JRE 只包含需要的模块(jlink工具)1.2 代码案例// ============ 模块描述文件示例 ============// ---- 模块 A:com.example.service ----// 文件路径:src/com.example.service/module-info.javamodulecom.example.service{requiresjava.sql;// 依赖 JDBCrequirestransitivejava.logging;// 传递依赖(使用方也能用日志)exportscom.example.service.api;// 只暴露 api 包exportscom.example.service.model;// 暴露 model 包}// ---- 模块 B:com.example.app ----// 文件路径:src/com.example.app/module-info.javamodulecom.example.app{requirescom.example.service;// 依赖模块 Arequiresjava.logging;// 直接使用日志}// ---- 模块 C:服务提供者 ----// 文件路径:src/com.example.service.impl/module-info.javamodulecom.example.service.impl{requirescom.example.service;providescom.example.service.api.UserServicewithcom.example.service.impl.UserServiceImpl;}// ============ 使用 jlink 创建自定义 JRE ============// 命令:只包含需要的模块// jlink --module-path mods --add-modules com.example.app \// --output custom-jre --strip-debug --compress 2// ============ 编译和运行模块 ============// 编译模块:// javac -d mods/com.example.service src/com.example.service/module-info.java src/com.example.service/com/example/service/api/*.java// 运行模块:// java --module-path mods --module com.example.app/com.example.app.Main2. JShell 交互式编程工具2.1 概述JShell 是 JDK 9 引入的REPL(Read-Eval-Print-Loop)工具,允许在命令行中交互式地编写和执行 Java 代码片段,无需创建完整的类和main方法。核心特点:即时执行代码片段(表达式、语句、方法、类)自动补全(Tab 键)前向引用(先使用方法再定义)保留历史记录(/history)支持自定义脚本(/open)错误反馈即时可见常用命令:命令说明/help帮助信息/list列出所有代码片段/edit打开编辑器/save file保存到文件/open file加载文件/exit退出/reset重置状态/vars列出所有变量/methods列出所有方法/types列出所有类型/imports列出所有导入/history历史记录/drop id删除指定片段2.2 代码案例// ============ 启动 JShell ============// 命令行输入:jshell// ============ 基本使用 ============// 直接计算表达式jshell2+3$1==5// 声明变量jshellStringname="JDK 9"name=="JDK 9"// 使用变量jshellSystem.out.println("Hello "+name)HelloJDK9// 定义方法(无需类包装)jshellintfactorial(intn){...returnn=1?1:n*factorial(n-1);...}|created methodfactorial(int)// 调用方法jshellfactorial(5)$5==120// 前向引用(先调用后定义)jshellSystem.out.println(greet("World"))Hello,World!jshellStringgreet(Stringname){...return"Hello, "+name+"!";...}|created methodgreet(String)|已更新方法greet(String)的引用// ============ 导入包 ============jshellimportjava.time.*jshellLocalDate.now()$8==2024-01-15// ============ 查看历史 ============jshell/list1:2+32:Stringname="JDK 9"3:System.out.println("Hello "+name)4:intfactorial(intn){returnn=1?1:n*factorial(n-1);}5:factorial(5)// ============ 保存和加载脚本 ============jshell/save myscript.jsh jshell/openmyscript.jsh// ============ 退出 ============jshell/exit|再见// ============ 命令行参数 ============// jshell --class-path libs/* 指定类路径// jshell --startup myinit.jsh 指定启动脚本// jshell -v 详细模式// jshell -q 安静模式3. 集合工厂方法(Collection Factory Methods)3.1 概述JDK 9 为集合接口引入了of()和ofEntries()工厂方法,可以简洁地创建不可变集合(Immutable Collections)。这是对 JDK 1.8 中Collections.unmodifiableList()和 GuavaImmutableList.of()的标准化替代。核心特点:返回不可变集合(add/remove抛UnsupportedOperationException)不允许null元素(立即抛NullPointerException)内存高效(内部使用紧凑数据结构)支持 0~10 个参数的重载 + 可变参数重载序列化支持支持的方法:接口方法ListList.of(),List.of(e1, e2, ...),List.of(E[])SetSet.of(),Set.of(e1, e2, ...),Set.of(E[])MapMap.of(),Map.of(k1,v1, k2,v2, ...),Map.ofEntries(Map.Entry...)Map.EntryMap.entry(K key, V value)3.2 代码案例// ============ 1. List.of() 创建不可变列表 ============// JDK 8 写法ListStringoldList=Collections.unmodifiableList(Arrays.asList("a","b","c"));// JDK 9 写法ListStringlist1=List.of("a","b","c");ListStringlist2=List.of();// 空列表ListStringlist3=List.of("single");// 单元素// 不可变性验证try{list1.add("d");// 抛出 UnsupportedOperationException}catch(UnsupportedOperationExceptione){System.out.println("不可变集合,无法修改");}// 不允许 nulltry{ListStringnullList=List.of("a",null,"c");// 抛出 NullPointerException}catch(NullPointerExceptione){System.out.println("不允许 null 元素");}// ============ 2. Set.of() 创建不可变集合 ============SetStringset1=Set.of("a","b","c");SetIntegerset2=Set.of(1,2,3,4,5);SetStringemptySet=Set.of();// 自动去重(注意:重复元素会抛异常,不同于 Set 构造器)try{SetStringdupSet=Set.of("a","a","b");// 抛出 IllegalArgumentException}catch(IllegalArgumentExceptione){System.out.println("Set.of() 不允许重复元素");}// ============ 3. Map.of() 创建不可变映射 ============// 最多 10 个键值对的重载MapString,Integermap1=Map.of("one",1,"two",2,"three",3);// 空 MapMapString,StringemptyMap=Map.of();// ============ 4. Map.ofEntries() 超过 10 个键值对 ============MapString,IntegerbigMap=Map.ofEntries(Map.entry("a",1),Map.entry("b",2),Map.entry("c",3),Map.entry("d",4),Map.entry("e",5),Map.entry("f",6),Map.entry("g",7),Map.entry("h",8),Map.entry("i",9),Map.entry("j",10),Map.entry("k",11)// 超过 10 个必须用 ofEntries);// ============ 5. 实际应用场景 ============// 常量集合publicclassConstants{publicstaticfinalListStringSUPPORTED_FORMATS=List.of("json","xml","yaml","csv");publicstaticfinalSetStringRESERVED_KEYWORDS=Set.of("if","else","for","while","return","class");publicstaticfinalMapString,StringMIME_TYPES=Map.of("json","application/json","xml","application/xml","html","text/html","txt","text/plain");}// 方法参数快速构建publicvoidprocessConfig(){MapString,Objectconfig=Map.of("host","localhost","port",8080,"timeout",3000);// 传递给处理方法}// Stream 中快速构建ListStringresult=List.of("a","b","c").stream().map(String::toUpperCase).collect(Collectors.toList());// ============ 6. 与 JDK 8 写法对比 ============// JDK 8ListStringjdk8List=Collections.unmodifiableList(newArrayList(Arrays.asList("a","b","c")));// JDK 9ListStringjdk9List=List.of("a","b","c");// JDK 8MapString,Integerjdk8Map=newHashMap();jdk8Map.put("a",1);jdk8Map.put("b",2);MapString,IntegerunmodifiableMap=Collections.unmodifiableMap(jdk8Map);// JDK 9MapString,Integerjdk9Map=Map.of("a",1,"b",2);4. 接口私有方法4.1 概述JDK 9 允许在接口中定义private方法(包括private static方法),用于复用default方法和static方法中的公共逻辑,避免代码重复。规则:private方法只能在接口内部被default或static方法调用不能被实现类访问private static方法可被其他static方法和default方法调用private非静态方法只能被default方法调用4.2 代码案例// ============ 1. 接口私有方法 ============publicinterfaceLogger{// 默认方法defaultvoidlogInfo(Stringmessage){log("INFO",message);// 调用私有方法}defaultvoidlogError(Stringmessage){log("ERROR",message);// 调用私有方法}defaultvoidlogDebug(Stringmessage){log("DEBUG",message);// 调用私有方法}// 私有方法:复用日志格式化逻辑privatevoidlog(Stringlevel,Stringmessage){Stringtimestamp=java.time.LocalDateTime.now().toString();System.out.println("["+timestamp+"] ["+level+"] "+message);}}// ============ 2. 接口私有静态方法 ============publicinterfaceValidatorT{defaultbooleanvalidate(Tvalue){returncheckNotNull(value)checkValid(value);}staticTValidatorTof(java.util.function.PredicateTpredicate){returnvalue-checkNotNull(value)predicate.test(value);}// 私有静态方法:复用非空检查privatestaticTbooleancheckNotNull(Tvalue){if(value==null){thrownewIllegalArgumentException("Value must not be null");}returntrue;}// 抽象方法booleancheckValid(Tvalue);}// ============ 3. 实际应用:数据转换器接口 ============publicinterfaceDataConverterS,T{defaultTconvert(Ssource){if(isNull(source)){returngetDefault();}returndoConvert(source);}defaultListTconvertAll(ListSsources){if(isNull(sources)||sources.isEmpty()){returnList.of();}returnsources.stream().filter(s-!isNull(s)).map(this::doConvert).collect(Collectors.toList());}TdoConvert(Ssource)