JDK9 新特性目录导航
- 目录结构
- 模块化系统
- jshell
- 多版本兼容JAR
- 接口的私有方法
- 改进try-with-resourcs
- 改进砖石操作符
- 限制使用单独下划线标识符
- String存储结构变更
- 快速创建只读结合
- 增强Stream API
- 改进Optional 类
- 多分辨率图像 API
- 全新 HTTP客服端API
- 智能JAVA 编译工具
- 统一JVM 日志系统
- javadoc 的 HTML5 支持
- java 动态编译
目录结构
JDK9具体目录结构如下所示:
- bin: 该目录包含所有的命令。
- conf: 包含用户可以编辑的配置文件,例如以前位于jrelib 目录中的.properties 和 .policy 文件。
- include: 包含一些编译本地代码时使用的C/C++头文件。
- jmods: 包含JMOD 格式的平台模块,创建自定义运行映射时需要它。
- legal: 包含法律声明。
- lib: 包含非Windows 平台上动态链接的本地库,其子目录和文件不应由开发人员直接编辑或使用。
注:JDK9 目录中不再有jre子目录。
模块化系统
JDK9将JDK分成一组模块,可以在编译时,运行时或构建时进行组合。模块化可以减少内存开销;只需必要的模块,并非全部模块,可以简化各种类库和大型应用的开发和维护。思考如下示例:
模块目录结构:
modela的module-info.java的内容
1 module modela { 2 exports com.cnblogs.bean; 3 }
modelb的module-info.java的内容
1 module modelb { 2 requires modela; 3 }
modelb的Test类
1 package com.cnblogs.tset; 2 3 import com.cnblogs.bean.Person; 4 5 public class Test { 6 7 public static void main(String[] args) { 8 Person person = new Person("念念就忘",20); 9 System.out.println(person); 10 } 11 12 }
输出结果:
Person{name='念念就忘', age=20}
如上示例,如果需要在modelb项目中使用modela项目的内容,必须在modela项目的module-info.java中定义exports com.cnblogs.bean将该目录下可以被其他模块使用,如果没有写,则包默认是封装在模块下,不被外界使用。而在modela项目中需要使用requires modela导入需要使用的模块名,则可以在modelb中使用modela定义exports的类(即才可以在Test类中使用Person类)。
module-info.java:该文件必须位于项目的根目录中。该文件用于定义模块需要什么依赖,以及那些包被外部使用。
exports:控制着那些包可以被其他模块访问到,所有不被exports的包默认都被封装在模块里面不被外界所使用。
requires:指明对其他模块的依赖。
jshell
JDK9新增了REPL(Read-Eval-Print Loop)工具jshell,jshell工具提供了一个交互式命令界面,可以评估声明,语句和表达式,无需编译即可返回执行结果。无论是在初学JAVA或学习新的API时都非常有用。
如上图所示,只需要在cmd中输入jshell命令,即可启动jshell界面。同时提醒你使用help intro可以大致了解该版本
如上图,jshell基本使用规则。每一条语句的末尾可以使用分好和不使用分好。
jshell也可以定义方法
退出jshell使用/exit
jshell详细介绍,请参考官方介绍文档
多版本兼容JAR
当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着库得去向后兼容你想要支持的最老的 Java 版本(许多情况下就是 Java 6 或者 Java7)。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 jar 功能能让你创建仅在特定版本的 Java 环境中运行库程序选择使用的 class 版本。
使用举例如下:
在指定目录【F:peterideaWorkjava9testsrcjavacomcnblogsTestJar.java】新建TestJar.java文件
1 package com.cnblogs; 2 3 public class TestJar { 4 5 public String getValue(){ 6 return "JAVA 8"; 7 } 8 9 }
在指定目录【F:peterideaWorkjava9testsrcjavacomcnblogsTest.java】新建Test.java文件
1 package com.cnblogs; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 TestJar testJar = new TestJar(); 7 System.out.println(testJar.getValue()); 8 } 9 10 }
在指定目录【F:peterideaWorkjava9testsrcjava9comcnblogsTestJar.java】新建TestJar.java文件,这个是向后兼容JDK9的文件
1 package com.cnblogs; 2 3 public class TestJar { 4 public String getValue(){ 5 return "JAVA 9"; 6 } 7 }
执行如下命令进行编译:
F:peterideaWorkjava9test>javac -d build --release 8 src/java/com/cnblogs/*.java
F:peterideaWorkjava9test>javac -d build9 --release 9 src/java9/com/cnblogs/TestJar.java
F:peterideaWorkjava9test>jar --create --main-class=Test --file multijar.jar -C build . --release 9 -C build9 .
在JDK9下运行如下命令,输出结果为 JAVA 9
F:peterideaWorkjava9test>java -version
openjdk version "9"
OpenJDK Runtime Environment (build 9+181)
OpenJDK 64-Bit Server VM (build 9+181, mixed mode)
F:peterideaWorkjava9test> java -cp multijar.jar com.cnblogs.Test
JAVA 9
在JDK8下运行如下命令,输出结果为JAVA 8
F:peterideaWorkjava9test>java -version
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
F:peterideaWorkjava9test>java -cp multijar.jar com.cnblogs.Test
JAVA 8
接口的私有方法
在JDK8中接口运行使用静态方法和默认方法后,JDK9可以在接口中使用私有方法。如下示例:
1 public interface MyInterface { 2 3 void normalInterfaceMethod(); 4 5 default void defaultMethod1(){ 6 privateMethod(); 7 } 8 9 default void defaultMethod2(){ 10 privateMethod(); 11 } 12 13 //接口的私有方法可以在JDK9中使用 14 private void privateMethod(){ 15 System.out.println("这是一个接口的私有方法"); 16 } 17 }
1 public class MyClass implements MyInterface{ 2 3 @Override 4 public void normalInterfaceMethod() { 5 System.out.println("实现接口的默认方法"); 6 } 7 }
1 public class Test { 2 public static void main(String[] args) { 3 MyClass myClass = new MyClass(); 4 myClass.normalInterfaceMethod(); 5 myClass.defaultMethod1(); 6 myClass.defaultMethod2(); 7 } 8 }
输出结果如下:
实现接口的默认方法
这是一个接口的私有方法
这是一个接口的私有方法
改进try-with-resources
JDK8中新增了try-with-resources语句,可以自动关闭需要关闭的资源文件。但是必须在try语句后的括号中初始化需要关闭的资源。在JDK9中改进了try-with-resources语句,你可以在try外初始化资源,然后在try后的括号中添加需要自动关的资源即可。如下示例:
在JDK8之前你只能这样去关闭资源需要,在finally语句中定义关闭操作,如下示例:
InputStreamReader reader = null; try { reader = new InputStreamReader(System.in); //...... } catch (IOException e) { e.printStackTrace(); }finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } }
在JDK8中,新增了try-with-resources语句,你可以在try后的括号中初始化资源,可以实现资源自动关闭。如下示例:
try (InputStreamReader reader = new InputStreamReader(System.in)) { //...... } catch (IOException e) { e.printStackTrace(); }
在JDK9中,改进了try-with-resources语句,可以在try外进行初始化,在括号内引用,即可实现资源自动关闭。如下示例:
1 InputStreamReader reader = new InputStreamReader(System.in); 2 OutputStreamWriter writer = new OutputStreamWriter(System.out); 3 //多资源用分号隔开 4 try (reader;writer) { 5 6 //...... 7 } catch (IOException e) { 8 e.printStackTrace(); 9 }
改进钻石操作符
JDK9中钻石操作符可以使用匿名实现类,可以在匿名实现类中重写方法等操作。
Set<String> set = new HashSet<>(){ //匿名实现类重写add方法。 @Override public boolean add(String s) { System.out.println("执行add方法"); return super.add(s); } }; set.add("1");
输出结果:
执行add方法
限制使用单独下划线标识符
在JDK8之前可以使用“_”单独的下划线作为标识符,但在JDK9中将单独的下划线标识符限制使用了,可能后期会将这个标识符做特殊处理如Lambda表达式一样的->操作符一样。如下示例:
在JDK8中可以单独使用“_”命名
String _ = "Hello";
System.out.println(_);
输出结果:
Hello
在JDK9中会抛出如下异常:
String 存储结构变更
从很多不同应用程序收集的信息表名,字符串是堆使用的主要组成部分,而且,大多数字符串对象只包含一个字符,这样的字符只需要一个字节的存储空间,因此这些字符串对象的内部char数组中有一半的空间被闲置。
JDK9之前String底层使用char数组存储数据private final char value[],JDK9将String底层存储数据改为byte数组存储数据private final byte[] value。
StringBuffer和StringBuilder也同样做了变更,将以往char数组改为byte数组。
快速创建只读集合
JDK9在List、Set和Map集合中新增of静态方法,快速创建只读集合。
在JDK9之前创建只读集合需要如下操作:
List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); list.add("C"); //设为只读List集合 list = Collections.unmodifiableList(list); System.out.println(list); Set<String> set = new HashSet<>(); set.add("E"); set.add("F"); set.add("G"); //设为只读Set集合 set = Collections.unmodifiableSet(set); System.out.println(set); Map<String, String> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); //设为只读Map集合 map = Collections.unmodifiableMap(map); System.out.println(map);
在JDK9中,可以使用of方法直接快速创建只读集合,如下示例:
List<String> list = List.of("A", "B", "C"); System.out.println(list); Set<String> set = Set.of("E", "F", "G"); System.out.println(set); Map<String, String> map = Map.of("k1", "v1", "k2", "v2", "k3", "v3"); System.out.println(map);
上面两则示例都输出一样的结果:
[A, B, C]
[E, G, F]
{k3=v3, k2=v2, k1=v1}
增强 Stream API
JDK9在Stream接口中新增4个方法:dropWhile、takeWhile、ofNullable,为iterate方法新增重载方法。
takeWhile
takeWhile可以用于从 Stream 中获取一部分数据,接受一个 Predicate 来进行选择,在有序的 Stream 中,takeWhile 返回从头开始的尽可能多的元素。
List<Integer> list = Arrays.asList(45,43,76,87,42,77,90,73,67,88);
list.stream().takeWhile((x) -> x < 80 ).forEach(System.out::println);
返回结果:
45 43 76
从返回结果可以看出,takeWhile将会按照list集合有序的从45开始到第一个不符合条件为止的所有结果。
dropWhile
dropWhile 的方法刚好与 takeWhile想法,返回剩余的元素。
List<Integer> list = Arrays.asList(45,43,76,87,42,77,90,73,67,88);
list.stream().dropWhile((x) -> x < 80 ).forEach(System.out::println);
输出结果:
87 42 77 90 73 67 88
从返回结果可以看出,dropWhile方法刚好和takeWhile方法形成互补,按照list集合有序的返回从第一个不满足条件元素开始到最后为止的所有结果。
ofNullable
在JDK8 中 Stream 不能完全为null,否则会报空指针异常。而在JDK9 中 ofNullable 方法允许创建一个为空的 Stream。
//NullPointerException //Stream<Object> stream1 = Stream.of(null); //System.out.println(stream1.count()); //不报异常 允许这样写 Stream<String> stringStream = Stream.of("AA", "BB", null); System.out.println(stringStream.count()); //不报异常 允许这样写 List<String> list = new ArrayList<>(); list.add("A"); list.add(null); System.out.println(list.stream().count()); //ofNullable() :允许值为 null Stream<Object> stream = Stream.ofNullable(null); System.out.println(stream.count()); Stream<String> stream2 = Stream.ofNullable("Hello World"); System.out.println(stream2.count());
输出结果:
3 2 0 1
iterate() 重载方法
//JDK8 使用iterate方法,需配合limit截止。 Stream.iterate(1, (x) -> x + 1).limit(10).forEach(System.out::print); System.out.println(); //JDK9 使用iterate的重载方法可以直接使用Predicate来截止。 Stream.iterate(1,(x) -> x <= 10, (x) -> x + 1).forEach(System.out::print);
改进 Optional 类
Optional 类是在JDK8中新增的类,主要是为了解决空指针异常。在JDK9中对这个类进行了改进,主要是新增了三个方法:stream,ifPresentOrElse 和 or 。
stream
stream方法将Optional转为一个 Stream,如果Optional 没有值就返回一个 Stream.empty。
List<String> list = List.of("A", "B", "C", "D", "E", "F"); Optional<List<String>> optional = Optional.of(list); optional.stream().forEach(System.out::println); Optional<Object> optional1 = Optional.empty(); System.out.println(optional.stream().count());
输出结果:
[A, B, C, D, E, F]
1
ifPresentOrElse
ifPresentOrElse方法:
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
如果 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction。如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()。
//如果optional包含值,执行action.accept方法。 Optional<Integer> optional = Optional.of(1); optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> System.out.println("没有值.")); optional = Optional.empty(); //如果optional不包含值,执行emptyAction.run方法。 optional.ifPresentOrElse( x -> System.out.println("Value: " + x),() -> System.out.println("没有值."));
输出结果:
Value: 1
没有值.
or
or方法:
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
如果Optional有值,返回 Optional 指定的值,否则返回一个预设的值。
//如果有值返回值。 Optional<String> optional1 = Optional.of("1"); Supplier<Optional<String>> supplierString = () -> Optional.of("没有值"); optional1 = optional1.or(supplierString); optional1.ifPresent( x -> System.out.println("Value: " + x)); //如果没值返回预先设定的值。 optional1 = Optional.empty(); optional1 = optional1.or( supplierString); optional1.ifPresent( x -> System.out.println("Value: " + x));
输出结果:
Value: 1
Value: 没有值
多分辨率图像 API
在 java.awt.image 包下新增了支持多分辨率图片的API,用于支持多分辨率的图片。
- 将不同分辨率的图像封装到一张(多分辨率的)图像中,作为它的变体。
- 获取这个图像的所有变体。
- 获取特定分辨率的图像变体,表示一张已知分辨率单位为 DPI 的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。
- java.awt.image.MultiResolutionImage接口的基础实现java.awt.image.BaseMultiResolutionImage获取所需要的变体。
- 通过接口的getResolutionVariant(double destImageWidth, double destImageHeight)方法,根据分辨率获取图像。
全新的 HTTP 客服端 API
HTTP,用于传输网页的协议,早在 1997 年就被采用在目前的 1.1版本中。直到 2015 年,HTTP2 才成为标准。
HTTP/1.1和HTTP/2的主要区别是如何在客户端和服务器之间构建和传输数据。HTTP/1.1 依赖于请求/响应周期。 HTTP/2 允许服务器“push”数据:它可以发送比客户端请求更多的数据。 这使得它可以优先处理并发送对于首先加载网页至关重要的数据。
JDK9 中有新的方式来处理 HTTP 调用。它提供了一个新的HTTP客户端(HttpClient),它将替代仅适用于blocking模式的HttpURLConnection(HttpURLConnection是在HTTP 1.0的时代创建的,并使用了协议无关的方法),并提供对 WebSocket 和 HTTP/2 的支持。
此外,HTTP客户端还提供 API 来处理 HTTP/2 的特性,比如流和服务器推送等功能。
全新的 HTTP 客户端 API 可以从 jdk.incubator.httpclient 模块中获取。因为在默认情况下,这个模块是不能根据 classpath 获取的,需要使用 add modules 命令选项配置这个模块,将这个模块添加到 classpath中。
智能 JAVA 编译工具
智能 java 编译工具( sjavac )的第一个阶段始于 JEP139 这个项目,用于在多核处理器情况下提升 JDK 的编译速度。如今,这个项目已经进入第二阶段,即 JEP199,其目的是改进 Java 编译工具,并取代目前 JDK 编译工具 javac,继而成为 Java 环境默认的通用的智能编译工具。
JDK 9 还更新了 javac 编译器以便能够将 java 9 代码编译运行在低版本 Java 中。
统一的 JVM 日志系统
日志是解决问题的唯一有效途径:曾经很难知道导致 JVM 性能问题和导致 JVM 崩溃的根本原因。不同的 JVM 日志的碎片化和日志选项(例如:JVM 组件对于日志使用的是不同的机制和规则),这使得 JVM 难以进行调试。
解决该问题最佳方法:对所有的 JVM 组件引入一个单一的系统,这些 JVM 组件支持细粒度的和易配置的 JVM 日志。
javadoc 的 HTML5 支持
JDK8 生成的java帮助文档是在 HTML4 中。而HTML4 已经是很久的标准了。
JDK9 的javadoc,现支持HTML5 标准。
下图是JDK8 API的HTML页面
如下图是JDK9 API的 HTML,右上角支持搜索功能。
java 动态编译器
JIT(Just-in-time)编译器可以在运行时将热点编译成本地代码,速度很快。但是 Java 项目现在变得很大很复杂,因此 JIT 编译器需要花费较长时间才能热身完,而且有些 Java 方法还没法编译,性能方面也会下降。AoT 编译就是为了解决这些问题而生的。
在 JDK 9 中, AOT(JEP 295: Ahead-of-Time Compilation)作为实验特性被引入进来,开发者可以利用新的 jaotc 工具将重点代码转换成类似类库一样的文件。虽然仍处于试验阶段,但这个功能使得 Java应用在被虚拟机启动之前能够先将 Java 类编译为原生代码。此功能旨在改进小型和大型应用程序的启动时间,同时对峰值性能的影响很小。
但是 Java 技术供应商 Excelsior 的营销总监 Dmitry Leskov 担心 AoT 编译技术不够成熟,希望 Oracle 能够等到 Java 10时有个更稳定版本才发布。