您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Java9-17新版本特性介绍
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Java9-17新版本特性介绍
自猿其说Tech
2021-12-22
IP归属:未知
30640浏览
计算机编程
最新版的Java17如期在2021年9月14号发布了,自从Java更改了版本迭代计划,每半年发一次新版本,每3年发一次长期支持(LTS)的版本,Java的版本号就蹭蹭的往上涨。最新的Java17也是一个长期支持的版本。虽然从9开始的新版本发布的特性大体了解,但还没系统整理过。这次大体整理了下几个主要的特性,先后与版本顺序无关。 #### 1 模块化JDK Java8及之前的Java项目的层级是class -> package -> jar,Java9是在package和jar包直接新增了一个层级——模块(module)。新版本的目录结构也相应发生了变化,原来的整个rt.jar包拆分成了七十几个模块,放在了jmods下。引入模块化后访问权限控制方面更加精细,之前当我们引入一个jar包,就可以访问jar包内所有的公共类和公共方法,甚至可以通过反射访问内部类和私有方法,现在模块可以控制只能访问指定的类。 当然使用新版本的Java,如果我们不想编写模块化代码,那代码编码和Java8是没有区别。如果要编写模块化代码,先要在src/main/java目录下新增一个module-info.java文件,就相当于模块的配置文件。如果业务代码中需要依赖非java.base模块的代码,就需要先在 module-info.java中引入相应的模块(JetBrains IDEA会自动提示)。java.base模块无需显式声明,因为是根模块,相当于class中的Object类,是默认导入的。 ![](//img1.jcloudcs.com/developer.jdcloud.com/b0ca42fa-35b0-4c68-b868-877f649d692120211222142510.png) 其中Java为 module-info.java 设计了专用的语法: ```java [open] module <module> { requires [transitive] <module>; exports <package> [to <module>,...]; opens <package> [to <module>,...]; provides <interface | abstract class> with <class1>,..; uses <interface | abstract class>; } ``` - [open] module : 声明一个模块,模块名称应全局唯一,不可重复。加上 open 关键词表示模块内的所有包都允许通过 Java 反射访问,模块声明体内不再允许使用 opens 语句。 - requires [transitive] : 声明模块依赖,一次只能声明一个依赖,如果依赖多个模块,需要多次声明。加上 transitive 关键词表示传递依赖,比如模块 A 依赖模块 B,模块 B 传递依赖模块 C,那么模块 A 就会自动依赖模块 C,类似于 Maven。 - exports [to [, ...]]: 导出模块内的包(允许直接 import 使用),一次导出一个包,如果需要导出多个包,需要多次声明。如果需要定向导出,可以使用 to 关键词,后面加上模块列表(逗号分隔)。 - opens [to [, ...]]: 开放模块内的包(允许通过 Java 反射访问),一次开放一个包,如果需要开放多个包,需要多次声明。如果需要定向开放,可以使用 to 关键词,后面加上模块列表(逗号分隔)。 - provides with [, ...]: 声明模块提供的 Java SPI 服务,一次可以声明多个服务实现类(逗号分隔)。 - uses : 声明模块依赖的 Java SPI 服务,加上之后模块内的代码就可以通过 java.util.ServiceLoader.load(Class) 一次性加载所声明的 SPI 服务的所有实现类。 上面说了java.base是根模块,那可以以它为例,看一下标准模块的格式。 ```java module java.base { // 因为是根模块,没有requires // 导出 java.io包、java.lang包 exports java.io; exports java.lang; // ... // 仅java.compiler、jdk.compiler、jdk.jshell这三个模块可以引入jdk.internal.javac包 exports jdk.internal.javac to java.compiler, jdk.compiler, jdk.jshell; // ... // ServiceLoader类会加载ToolProvider的所有实现,具体实现类可以由其他模块实现 uses java.util.spi.ToolProvider; // 对外提供SPI服务,SecureRandom、Random、SplittableRandom是具体实现类 provides java.util.random.RandomGenerator with java.security.SecureRandom, java.util.Random, java.util.SplittableRandom; } ``` #### 2 新的HTTP/2 客户端 Java11定义了一个新的HTTP客户端API来实现HTTP/2和WebSocket,来替换旧的HttpURLConnectionAPI。 现有的HttpURLConnectionAPI设计早于HTTP/1.1,不支持HTTP/2和WebSocket,仅支持阻塞模式,兼容了多种已经过时的协议,并且设计过于抽象,导致使用非常困难。新的HTTP客户端的优势是简单和易于使用,简易的API就可以满足80%~90%的需求;性能和内存消耗与Apache HttpClient 库以及 Netty 和 Jetty 相当; 支持HTTP/1.1、HTTP/2、HTTP/TLS、WebSocket等协议。 下面是一个简单的同步GET请求: ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder(URI.create("http://openjdk.java.net/")).GET().build();HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); ``` HttpClient类是用于保存HTT请求的配置的容器。 HttpRequest类是表示发送到服务器的HTTP请求。 HttpResponse类表示HTTP响应。也可以发送异步请求: ```java HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.java.net/")) .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); ``` 如果有其他需求,只需增加相应的配置即可。如 ```java HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .followRedirects(HttpClient.Redirect.ALWAYS) .connectTimeout(Duration.ofSeconds(20)) //.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80))) //.authenticator(Authenticator.getDefault()) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.java.net/")) .header("Content-Type", "application/json") .timeout(Duration.ofMinutes(1)) .POST(HttpRequest.BodyPublishers.ofFile(Paths.get("file.json"))) .build(); client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(Path.of("tmp.html"))) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); ``` #### 3 新的集合/Stream方法 Java8想要创建少量元素的不可变集合或Map,要么引入Guava包,要么自己一个一个的add(),非常的不便。Java9新增了of()和copyOf()方法,创建集合一目了然。 ```java Set<Integer> set = Set.of(1, 2, 3); List<Character> list = List.of('a', 'b', 'c'); Map<String, String> map = Map.of("k1", "v1", "k2", "v2", "k3", "v3"); Set<Integer> set2 = Set.copyOf(set); ``` of()方法创建的都是不可变集合ImmutableCollections,copyOf()方法如果入参就是ImmutableCollections则直接返回,否则就创建一个ImmutableCollections。 Stream 新增了几个方法:dropWhile、takeWhile、ofNullable,为 iterate 方法新增了一个重载方法: ```java // 一直往后取,直到返回false就停止 Stream.of(1, 2, 3, 2, 1).takeWhile(t -> t < 3).forEach(System.out::print); // 12 // 为true就丢弃,直到碰到false就返回后面的所有元素 Stream.of(1, 2, 3, 2, 1).dropWhile(t -> t < 3).forEach(System.out::print); // 321 // 之前的Stream.of()方法,如果只有一个元素且为null,会报空指针 // System.out.println(Stream.of(null).count()); // NPE // 返回0是因为ofNullable()方法判断如果入参为null,就返回Stream.empty() System.out.println(Stream.ofNullable(null).count()); // 0 System.out.println(Stream.of(null, null).count()); // 2 // 之前只有 iterate(T seed, UnaryOperator<T> f)方法,元素迭代缺少终止条件 Stream.iterate(1, t -> t < 10, t -> t + 1).forEach(System.out::print); // 123456789 ``` #### 4 局部变量类型推断 Java10开始可以用var来声明局部变量,它会根据等式右边的变量类型来假定其类型。但方法参数、构造函数参数、方法返回类型、类字段、异常捕获类型不允许用var来声明。经反编译可知,var仅存在于编译时,编译后的字节码的变量类型为实际的类型。 ```java public class HelloWorld { public static void main(String[] args) { var s = "hello world"; for (var c : s.toCharArray()) { } } } ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/1d8c30ba-f74e-4af2-841e-9e9c7297f72820211222142810.png) #### 5 紧凑字符串和文本块 Java8的String内部使用一个char[]来保存UTF-16字符串。一个char占用2个字节,如果字符串都是ASCII字符,那就相当于浪费了一半空间。Java9的String内部使用byte[]代替char[],如果字符串都是ISO-8859-1/Latin-1字符(编码0-255,包含ASCII码、以及一些西欧语言),则使用ISO-8859-1/Latin-1编码(一个字符占用一个字节)存储字符串,否则就使用UTF-16编码(一个字符占用2字节或4字节)存储。 文本块就是一个多行字符串,用三个双引号包裹,省去了之前手动拼接字符串。 ```java String html = """ <html> <body> <p>Hello, world</p> </body> </html> """; ``` 而且它会自动将每行前面的空白删除,空白长度就是最后一行"""的空白长度。 #### 6 switch模式匹配 switch有多难用就不用我多说了吧,无效代码太多,而且有时忘写了break或default,还会导致程序bug,还不如多写几个else if。新版的Java对switch语法进行了改进,支持"case L ->"标签,新增了yield取值关键字,虽然还是不如else if灵活,但新的switch更加简洁明了。 ```java switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } ``` 现在switch支持直接返回值,如果有代码块,使用yield返回: ```java int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> { System.out.println("error code, return default vlaue: 0"); yield 0; // yield a value } }; ``` #### 7 instanceof的模式匹配 相当于是增强的 instanceof,使得instanceof 变得更简洁、更安全。 ```java // 之前(未校验NPE) if (obj instanceof String) { String s = (String) obj; return s.equals("test"); } // 现在: if (obj instanceof String s) { return s.equals("test"); } // 当然也可以合并到一条语句 return (obj instanceof String s) && s.equals("test"); ``` #### 8 record记录类和sealed封闭类 record是一种新的类关键字,类似于enum,可以很方便的定义不变的数据类。 ```java record Point(int x, int y) { } ``` ![](//img1.jcloudcs.com/developer.jdcloud.com/a0cb77c0-9569-432c-8d0e-e3e3183b025a20211222143011.png) 编译后由字节码可知,Point是继承自java.lang.Record的一个不可变类,自动实现了构造函数,equals(),hashCode(), toString() 以及x(),y()方法,减少了样板代码。 sealed修饰类或接口,可以密封类和接口,限制哪些其他类或接口可以扩展或实现它们,下面的例子说明只有Circle、Rectangel、Square三个类可以继承,并且这三个类必须final修饰,即不可变类。 ```java public abstract sealed class Shape permits Circle, Rectangle, Square { ... } ``` #### 9 私有接口方法 Java8中接口中可以定义常量、抽象方法、默认方法、静态方法,Java9接口又支持定义私有方法和静态私有方法。现在接口的能力和抽象类已经差不多了,那接口和抽象类两者还有什么区别呢。我觉得功能不同,接口是行为抽象,而抽象类是逻辑抽象。 ```java public interface Man { // 常量 public static final int MIN_AGE = 1; // 抽象方法,实现类必须实现 void eat(); // 默认方法,实现类可以覆盖 default void learn() { System.out.println("learn"); } // 静态方法,实现类无法覆盖,其他类可以直接调用 static void sport() { System.out.println("sport"); } // 仅本接口可以使用 private void work() { System.out.println("work"); } // 仅本接口可以使用 private static void hardWork() { System.out.println("hard work"); } // 接口也可以定义类 class Young { } } ``` #### 10 jshell命令行工具 Java 9 中引入了交互式编程环境(REPL),这就是JShell,它允许可以执行 Java脚本代码,并且立即返回结果。 ![](//img1.jcloudcs.com/developer.jdcloud.com/38f27cb2-6562-4d43-8ab1-21a057f04a6220211222143108.png) #### 11 新的垃圾收集器 Java8默认的垃圾收集器是ParallelGC,即Parallel Scaveng(新生代)和 Parallel Old(老年代)。对于准备过面试的同学应该对他俩不会陌生,什么新生代用“复制”算法,老年代用“标记-整理”算法,还有一大堆看过就忘的调优参数。Java9的默认垃圾收集器已经变成了G1。 G1(Garbage-First)的设计原则是首先回收垃圾最多的区域。它的工作原理和CMS类似,在逻辑上也是区分新生代(eden区+survivor区)和老年代,也是分代进行标记清理。不同之处是G1把堆分成了多个大小相同的区域(region),这些区域可能是不同代的。并发标记过程中,可以知道哪些区域的垃圾比例较高,就专门对这些区域进行回收,可以获得较高的性价比。 后面又新增了几种垃圾收集器,Java11新增了ZGC,Java12新增了Shenandoah GC。这两种都是减少GC的停顿时间为目标的。这两种GC的停顿时间都不超过10ms。ZGC和Shenandoah GC与G1类似,都是把内存分成多个Region,整体GC思路大体类似。但都取消了分代概念,同时引入了染色指针、读屏障、内存多重映射等技术。 有增就有减,新版本移除CMS(Concurrent Mark Sweep)垃圾收集器,并且弃用Parallel Scavenge和Serial Old垃圾收集算法的组合。 #### 12 最后 这几个版本的更新还是挺多的,本次就简单的挑了几点整理了一下,肯定有很多遗漏的地方,欢迎补充说明,本文就算抛砖引玉了。具体的更新日志可以查询openjdk官网。 ------------ ###### 自猿其说Tech-京东物流技术发展部 ###### 作者:高宗正(攀登者小组)
原创文章,需联系作者,授权转载
上一篇:Pigeon在Flutter多端接口统一中的应用
下一篇:超大规模图技术在京东电商场景中如何实现降本增效
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号