本文为【Java新版特性企业级详解】系列文章合集之一,旨在介绍java8-18全部更新功能,本文为第6篇
Oracle在2020年3月17日宣布JAVA14 全面上市,JAVA14通过每六个个月发布一次新功能,为企业和开发人员社区提供增强功能,继续了Oracle加快创新的承诺。
最新的JAVA开发工具包提供了新功能,其中包括两项备受期待的新预览功能,实例匹配的匹配模式(JEP 305) 和记录(JEP 359),以及文本块的第二个预览(JEP 368),此外,最新的JAVA版本增加了对switch表达式的语言支持,公开了用于持续监控JDK Flight Recorder数据的新API,将低延迟的ZGC收集器的可用性扩展到了macOS和Windows,并在孵化器模块中添加了包装完备的java应用程序和新的外部内存访问API,以安全高效的访问JAVA对外部的内存。
我们可以在openjdk中观察到JDK14发布的详细官方计划和具体新特性详情,地址如下:https://openjdk.java.net/projects/jdk/14/
JAVA14 一共发行了16个JEP(JDK Enhancement Proposals,JDK 增强提案)
语言特性7项目:
垃圾回收修改
新增工具
增加废弃和移除
以往我们使用instanceof运算符都是先判断,然后在进行强转,例如我们查看String的equals方法源码:
public boolean equals(Object anObject) { if (this == anObject) { return true; } // 先进行类型的判断 if (anObject instanceof String) { // 然后进行强转 String aString = (String)anObject; if (!COMPACT_STRINGS || this.coder == aString.coder) { return StringLatin1.equals(value, aString.value); } } return false;}需要先判断类型然后强转,还要声明一个本地变量,语法比较麻烦。
比较理想的状态是,在执行类型检测的时候同时执行类型转换。
JEP305 新增了使instanceof运算符具有模式匹配的能力。模式匹配能够使程序的通用逻辑更加简洁,代码更加简单,同时在做类型判断和类型转换的时候也更加安全。
新的instanceof模式匹配语法是:在instanceof的类型之后添加了变量,如果对obj的类型检查通过,obj会被转换成后面的变量表示的数据类型,数据类型的声明仅仅书写一次即可。
Object obj ="hello java";if(obj instanceof String str){ System.out.println(str);}else{ System.out.println("not a String");}上述语法的判断逻辑时,如果obj是String类型,则会转换为后面的str,如果不是,则执行else,注意,此时的str仅仅是if语句块里的局部变量,在else语句块中不可用。
Object obj ="hello java";// 这里做的是取反运算if(!(obj instanceof String str) ){ System.out.println("not a String"); //System.out.println(str);// 这里不能使用str}else{ System.out.println(str);// 这里可以使用str}但是如果if语句中使用了! 这种取反运算,那么逻辑上就是相反的,这个时候else才是相当于成功转换了,所以在else中可以使用str,if中不可以使用str。
Object obj =new Date();// "hello java";if(obj instanceof String str && str.length()>2){ System.out.println(str);}else{ System.out.println("not a String or length <=2");}上述语句块中,如果if中的判断逻辑比较复杂,是可以在后续的其他条件中使用str变量进行判断的,但是注意这里的运算符是短路与运算,就是要保证后面在使用str时,已经完成了转换,如果使用短路或运算,无法保证str是可以成功转换的,是不允许的,如下面的代码,就是错的:
Object obj =new Date();// "hello java";if(obj instanceof String str || str.length()>2){ System.out.println(str);}else{ System.out.println("not a String or length <=2");}总之:if语句块中的小括号内,要保证成功的进行了转换才可以在if语句库中使用转换的对象,否则不可以。
通过这个模式匹配,我们可以简化在类中重写的equals方法:
class Person{ private String pname; private Integer page; public Person(String pname,Integer page){ this.pname =pname; this.page=page; } @Override public boolean equals(Object obj){ return obj instanceof Person p && Objects.equals(this.pname,p.pname)&& Objects.equels(this.page,p.page); }}java的Switch语句是一个一直在变化的语法,可能是因为之前的不够强大,在JAVA14中,我们依然可以看到对于switch的语法优化。
我们简单整理一下switch语句在各个版本中的特点
JAVA5 switch变量类型可以使用枚举了;
JAVA7 switch变量类型中可以使用String;
JAVA11 switch语句可以自动省略break导致的贯穿提示警告 case L ->;
JAVA12 switch语句可以作为表达式,用变量接收结果,可以省略break;
JAVA13 switch中可以使用yield关键字停止switch语句块;
JAVA14 JEP361switch表达式(标准)是独立的,不依赖于JEP 325 和 JEP 354,也就是说这里开始,之前学习的switch语句的语法成为一个正式的标准。未来是否有更多的改进,我们可以拭目以待。
JDK12对缺省break的贯穿弱点进行了改进,case: 改成 case L -> ,这样即使不写break也不会贯穿了,而且可以作为表达式返回结果。
var grade ="a";var res =switch(grade){ case "a" -> "优秀"; case "b" -> "良好"; case "c" -> "一般"; case "d" -> "及格"; default -> "no such grade";}JAVA12 开始也可以进行多值匹配的支持:
var grade ="a";var res =switch(grade){ case "a","b" -> "优秀"; case "c" -> "一般"; case "d" -> "及格"; default -> "no such grade";}JAVA13开始可以使用 yield返回结果,这里的case后面仍然使用冒号:
String x = "3";int i = switch (x) { case "1": yield 1; case "2":{ System.out.println(""); yield 2; } default: yield 3;};System.out.println(i);文本块在JAVA13中开始了第一次的预览,目标是在字符串中可以更好的表达 HTML XML SQL或者JSON格式的字符串,减少各种的不相关一些空格换行符号,字符串转义和字符串加号的拼接。
在JAVA14中,增加了两个escape sequence,分别是 \
目标:
String textBlock = """ Title \s\ \s """;System.out.println(textBlock);通过Record增强java编程语言,Record提供了一种紧凑的语法来声明类,这些类是浅层不可变数据的透明持有者。
我们经常听到这样的抱怨:"JAVA太冗长","JAVA规矩多"。最明显的就是最为简单数据载体的类,为了写一个数据类,开发人员必须编写许多低价值,重复,且容易出错的代码,构造函数,getter setter访问器,equals,hashcode,toString这些东西,尽管IDE可以提供一些插件和手段优化,但是仍然没有改变这些代码依然存在,需要操作的事实。
传统的类如下:
class Person{ private Integer pid; private String pname; private Integer page; @Override public String toString() { return "Person{" + "pid=" + pid + ", pname='" + pname + '\'' + ", page=" + page + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(pid, person.pid) && Objects.equals(pname, person.pname) && Objects.equals(page, person.page); } @Override public int hashCode() { return Objects.hash(pid, pname, page); } public Integer getPid() { return pid; } public void setPid(Integer pid) { this.pid = pid; } public String getPname() { return pname; } public void setPname(String pname) { this.pname = pname; } public Integer getPage() { return page; } public void setPage(Integer page) { this.page = page; } public Person() { } public Person(Integer pid, String pname, Integer page) { this.pid = pid; this.pname = pname; this.page = page; }}就算是使用IDE的快捷键,这些代码也是臃肿的。
Record是java的一种新的类型,同枚举一样,Record也是对类的一种限制,Record放弃了类通常享有的特性:将API和表示解耦,但是作为回报,record使数据类型变得非常简洁,一般可以帮助我们定义一些简单的用于传递数据的实体类。
一个record具有名称和状态描述,状态描述声明了record的组成部分:
record Person(String name ,int age){}因为record是数据的简单透明持有者,所以record会自动获取很多的标准成员:
public class MainClass { public static void main(String[] args) { Person p = new Person(1, "张三", 10); Person p2 = new Person(1, "张三", 10); System.out.println(p.pname()); System.out.println(p); System.out.println(p.hashCode()); System.out.println(p2.hashCode()); System.out.println(p.equals(p2)); }}record Person(Integer pid, String pname, Integer page) {};执行结果如下
Records的一些限制:
在record中声明额外的变量类型:
也可以显示声明从状态描述自动派生的任何成员,可以在没有正式参数列表的情况下声明构造函数,并且在正常的构造函数主体正常完成时调用隐式初始化,这样就可以在显示构造函数中仅执行其参数的验证逻辑,并且省略字段的初始化。
public class MainClass { public static void main(String[] args) { Person p =new Person(1,"张三",10); Person p2 =new Person(1,"张三",10); System.out.println(p.pname()); System.out.println(p); System.out.println(p.hashCode()); System.out.println(p2.hashCode()); System.out.println(p.equals(p2)); }}record Person(Integer pid,String pname ,Integer page){ // 定义额外的变量必须是静态的,不能定义成员变量 private static String name; public static void setName(String name){ Person.name=name; } // 可以定义其他实例方法 public void eat(){ System.out.println("eat"); } // 可以定义其他静态方法 public static void methodA(){ System.out.println("methoA"); } // 这里是构造函数,默认就是全参的构造函数,和record声明的参数列表是一致的, // 这里可以使用全参构造函数中的所有参数 // 在这里会默认执行参数给属性赋值操作,就是在这里默认会有this.pid=pid,this.pname=pname,this.page=page public Person{ System.out.println(pid); System.out.println(pname); System.out.println(page); }};由于维护和兼容性测试的成本,在JDK8时将Serial+CMS,ParNew+Serial Old这两个组合声明为废弃(JEP173),并在JDK9中完全取消了这些组合的支持(JEP214)。
ParallelScavenge+SerialOld GC 的GC组合要被标记为Deprecate了。
这个GC组合需要大量的代码维护工作,并且,这个GC组合很少被使用。因为它的使用场景应该是一个很大的Young区和一个很小的Old区,这样的话,Old区用SerialOld GC去收集停顿时间才可以勉强被接受。
废弃了Parallelyoung generationGC 与SerialOldGC组合 (-XX:+UseParallelGC 与 -XX:-UseParallelOldGC 配合开启),现在使用-XX:+UseParallelGC -XX:-UseParallelOldGC或者使用 -XX:-UseParallelOldGC会出现如下警告:
自从G1出现后,CMS在JDK9中就被标记为Deprecate了。
CMS弊端:
其他收集器:
G1回收器hotSpot已经默认使用有几年了,我们还看到两个新的GC JAVA11中的ZGC和openJDK12中的Shenandoah,后两者主要特点是:低停顿时间
Shenandoah非Oracle官方发布的,是OpenJDK于JAVA12发布的
收集器名称 | 运行时间 | 总停顿时间 | 最大停顿时间 | 平均停顿时间 |
Shenandoah | 387.602s | 320ms | 89.79ms | 53.01ms |
G1 | 312.052s | 11.7s | 1.24s | 450.12ms |
CMS | 286.264s | 12.78s | 4.39s | 852.26ms |
ParallelScavenge | 260.092s | 6.59s | 3.04s | 823.75ms |
JAVA14之前,ZGC仅仅支持Linux。
基于一些开发部署和测试的需要,ZGC在JDK14中支持在macOS 和windows,因此许多桌面级应用可以从ZGC中受益,目前还是一个实验性版本。
与Shenandoah目标非常相似,都是在尽量减少吞吐量的情况下,实现对任意堆大小(TB级)都可以把垃圾收集器停顿时间限制在10毫秒以内的低延迟时间。
ZGC 收集器是一款基于Region内存布局的,暂时不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现并发的标记压缩算法,以低延迟为首要目标的一款垃圾收集器。现在想在macOS 和windows上使用ZGC,方式如下:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC关于ZGC的一些测试数据:
ZGC目前还处于一个实验状态,但是性能非常亮眼,未来将在服务端,大内存,低延迟应用上作为首选的垃圾收集器。
NullpointerException是java开发中经常遇见的问题,在JDK14之前的版本中,空指针异常的提示信息就是简答的null,并不会告诉我们更加有用的信息,只是根据异常产生的日志来进行查找和处理,对于很长的引用来说,很难定位到具体是哪个对象为null。
这种提示其实并不是很详细,我们可以在运行代码的时候,加上一段配置,用以展示比较友好的控制成提示信息:
-XX:+ShowCodeDetailsInExceptionMessages输出的信息如下所示:
对于更复杂的代码,如果很长的调用,就无法确定是哪个变量为空,数组也是:
a.b.c.d=100;a[j][j][k]=99;NPE也可能在方法调用中传递:
x().y().i=99;这里如果出现了NPE,那么仅凭行号和文件名也是无法确定到底是x方法还是y方法的问题。
所以这个时候,我们需要使用ShowCodeDetailsInExceptionMessages来具体定位是哪个null。
该特征旨在创建一个用于打包独立java应用程序的工具。
JAVA应用的打包和分发一直都是个老大的难题,用户希望JAVA引用的安装和运行方式和其他应用有相似的体验。
比如,在windows上只需要双击文件就可以运行,JAVA平台本身没有提供实用的工具解决这个问题,通常都依赖第三方的工具完成,这个JEP的目标就是创建一个简单的JAVa打包工具jpackage, 相对于第三方工具,jpackage只适用于比较简单的场景,不过对很多应用来说已经足够好了。
该jpackage工具将java的应用程序打包到特定的平台的程序包中,该程序包包含所必须的依赖。该应用程序可以作为普通的jar文件或者模块的集合提供,受支持的特定平台的软件包格式为:
默认情况下,jpackage以最适合其运行系统的格式生成软件包。
如果有一个包含jar文件的应用程序,所有的应用程序都位于一个名为lib 的目录总,并且lib/main.jar包含主类,可以通过如下命令打包:
$ jpackage --name myapp -- input lib --main-jar main.jar将以本地系统的默认格式打包应用程序,将生成的打包文件保留到当前目录中。如果MANIFEST.MF文件中没有main.jar。没有Main-Class属性,则必须显式指定主类:
$ jpackage --name myapp --input lib --main-jar main.jar \ --main-class myapp.Main软件包的名称将为myapp,包括该应用程序的启动器,也称为myapp。启动程序将会从输入目录复制的每个jar文件放在jvm的类路径上。
如果您希望默认格式以外的其他格式制作软件包,请使用 --type选项。例如,要在macOS 上生成pkg文件而不是dmg文件:
$ jpackage --name myapp --input lib --main-jar main.jar --type pkg如果您有一个模块化应用程序,该程序由目录中的模块化jar文件或JMOD文件组成,并且模块中lib包含主类myAPP,则命令为:
$ jpackage -name myapp --moudule-path lib -m myapp如果myAPP模块未标识主类,则必须再次明确:
$ jpackage -name myapp --moudule-path lib -m myapp/myapp.MainJava Flight Recorder(JFR)是JVM的诊断和性能分析工具。
JAVA14之前只能做离线的分析,现在可以做实时的持续监视。
它可以收集有关JVM以及在其上运行的Java应用程序的数据。JFR是集成到JVM中的,所以JFR对JVM的性能影响非常小,我们可以放心的使用它。
具体使用比较复杂,建议直接阅读官网。
通过一个API,以允许java程序安全有效的访问JAVA堆之外的外部存储(堆以外的外部存储空间)。
许多java的库都能访问外部存储,例如 ignite,mapDB, memcached以及netty的ByteBuffer API,这样可以:
但是JAVAAPI本身没有提供一个令人满意的访问外部内存的解决方案。
当java程序需要访问堆内存之外的外部存储是,通常有两种方式:
ByteBuffer有自己的限制。首先是ByteBuffer的大小不能超过2G,其次是内存的释放依靠垃圾回收器,Unsafe的API在使用是不安全的,风险很高,可能会造成JVM崩溃。另外Unsafe本身是不被支持的API,并不推荐。
JEP 370的“描述”部分引入了安全高效的API来访问外部外部内存地址,目前该API还是属于孵化阶段,相关API在jdk.incubator.foreign模块的jdk.incubator.foreign包中, 三个API分别是: MemorySegment , MemoryAddress和MemoryLayout 。 MemorySegment用于对具有给定空间和时间范围的连续内存区域进行建模。 可以将MemoryAddress视为段内的偏移量。 最后, MemoryLayout是内存段内容的程序化描述。
JAVA14增加了一种文件映射模式,用于访问非易失性内存,非易失性内存能够持久保持数据,因此可以利用该特性来改进性能。
JEP352 可以使用FileChannelAPI创建引用非易失性内存,(non-volatile memory) 的MappedByteBuffer实例,该JEP建议升级MappedByteBuffer以支持对非易失性存储器的访问,唯一需要的API更改是FileChannel客户端,以请求映射位于NVM的支持的文件系统,而不是常规的文件存储系统上的文件,对MappedByteBuffer API最新的更改意味着他支持允许直接内存更新所需要的所有行为,并提供更高级别的JAVA客户端库所需要的持久性保证,以实现持久性的数据类型。
目标:
该JEP使用了JAVASE API的两个增强功能:
特定于JDK的API更改:通过新模块中的公共API公开新的MApMode枚举值。
一个公共扩展枚举ExtendedMapMode将添加到jdk.nio.mapmode程序包:
package jdk.nio.mapmode;public class ExtendedMapMode{ private ExtendedMapMode(){ } public static final MapMode READ_ONLY_SYNC= ... ... }在调用FileChannel::map方法创建映射到NVM设备文件上的只读或者写MappedByteBuffer时,可以使用上述的枚举值,如果这些标志在不支持NVM设备文件平台上传递,程序会抛出UnsupportedOperationException异常,在受支持的平台上,及当目标FileChannel实例是通过NVM设备打开的派生文件是,才能传递这些参数,在任何情况下,都会抛出IOException;
Java专业人士 原创文章
请【关注】【点赞】【收藏】
如需转载,请回复说明
| 留言与评论(共有 0 条评论) “” |