你好,我是看山。
本文收录在 《从小工到专家的 Java 进阶之旅》 系列专栏中。
从 2017 年开始,Java 版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动 Java 的发展。从 《JVM Ecosystem Report 2021》 中可以看出,目前开发环境中有近半的环境使用 Java8,有近半的人转移到了 Java11,随着 Java17 的发布,相信比例会有所变化。
因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。
概述
Java15 是在 2020 年 9 月发布的一个短期版本,新增特性如下:
- JEP 339:Edwards-Curve 数字签名算法
- JEP 360:密封的类和接口(预览功能)
- JEP 371:隐藏类
- JEP 372:移除 Nashorn JavaScript 引擎
- JEP 373:重新实现 DatagramSocket 接口
- JEP 374:禁用偏向锁
- JEP 375:instanceof 匹配模式(第二版预览功能)
- JEP 377:ZGC:可伸缩低延迟垃圾收集器
- JEP 378:文本块
- JEP 379:Shenandoah:低暂停时间垃圾收集器
- JEP 381:移除 Solaris 和 SPARC 端口 API
- JEP 383:外部存储器访问 API(第二版孵化功能)
- JEP 384:Record 类型(第二版预览功能)
- JEP 385:废除 RMI Activation
接下来我们一起看看这些特性。
Edwards-Curve 数字签名算法(JEP 339)
Edwards-Curve 数字签名算法(EdDSA),一种根据 RFC 8032 规范所描述的 Edwards-Curve 数字签名算法(EdDSA)实现加密签名。
EdDSA 是一种现代的椭圆曲线方案,与 JDK 中的现有签名方案相比,EdDSA 具有更高的安全性和性能,因此备受关注。它已经在 OpenSSL 和 BoringSSL 等加密库中得到支持,目前在区块链领域用的比较多。
我们看下官方给的例子:
byte[] msg = "Hello, World!".getBytes(StandardCharsets.UTF_8);
// example: generate a key pair and sign
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair kp = kpg.generateKeyPair();
// algorithm is pure Ed25519
Signature sig = Signature.getInstance("Ed25519");
sig.initSign(kp.getPrivate());
sig.update(msg);
System.out.println(Hex.encodeHexString(sig.sign()));
// example: use KeyFactory to contruct a public key
KeyFactory kf = KeyFactory.getInstance("EdDSA");
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdECPoint(true, new BigInteger("1")));
PublicKey pubKey = kf.generatePublic(pubSpec);
System.out.println(pubKey.getAlgorithm());
System.out.println(Hex.encodeHexString(pubKey.getEncoded()));
System.out.println(pubKey.getFormat());
例子中 Ed25519 是使用 SHA-512(SHA-2)和 Curve25519 的 EdDSA 签名方案。旨在提供与高质量 128 位对称密码相当的抗攻击能力,公钥长度为 256 位,签名长度为 512 位。
隐藏类(JEP 371)
Java15 引入了一个新的特性:隐藏类(Hidden Classes),一个专为框架而设计的特性。大多数开发人员不会直接使用这个特性,一般是通过动态字节码或 JVM 语言来使用隐藏类。
隐藏类有下面三个特点:
- 不可发现:在运行时生成内部类对象;
- 访问控制:只能通过反射访问,不能直接被其他字节码访问;
- 较短的生命周期:可独立于其他类加载、卸载,且效率很高,能够减少框架的内存占用。
隐藏类的功能特性还是比较有意思的,会涉及类加载、卸载、不可见、反射等很多内容,后续会开文单独聊,文章会放在 从小工到专家的 Java 进阶之旅 专栏中。
重新实现 DatagramSocket 接口(JEP 373)
老的 DatagramSocket API 在 Java15 中被重写,是继 Java14 重写 Socket API 的后续不走。这个特性是 Loom 项目的先决条件。
目前,DatagramSocket
和MulticastSocket
将所有的套接字委托为java.net.DatagramSocketImpl
的实现,根据不同的平台,Unix 平台使用PlainDatagramSocketImpl
,Windows 平台使用TwoStackPlainDatagramSocketImpl
和DualPlainDatagramSocketImpl
。抽象类DatagramSocketImpl
是 Java1.1 提供的,功能很少且有一些过时方法,阻碍了 NOI 的实现。
类似于 Java14 中对 Socket API 的重写(参见 Java14 新特性),会在DatagramSocket
内部封装一个DatagramSocket
实例,将所有调用直接委托给该实例。包装实例或者使用 NIO 的DatagramChannel::socket
创建套接字,或者是使用原始DatagramSocket
类的实现DatagramSocketImpl
实现功能(用于实现向后兼容)。
我们可以看下新的依赖图:
禁用偏向锁(JEP 374)
在 Java15 中,默认禁用偏向锁,弃用了所有相关命令行选项。
偏向锁是 HotSpot 中一种用于减少非竞争锁定开销的优化技术,不过在如今的应用程序中,优化增益不太明显了。
根据官方说法,使用偏向锁增益最多的是大量使用早期同步组件(比如Hashtable
、Vector
等),随着新的 API 实现和针对多线程场景引入的支持并发的数据结构,偏向锁的锁定及撤销,会带来性能的开销,从而是优化收益降低。
而且随着越来越多的功能特性引入,偏向锁在同步子系统中引入的大量代码,侵入 HotSpot 其他组件,带来代码的复杂性和维护成本,成为代码优化的阻碍。所以官方要将其移除。
不过,有些应用在禁用偏向锁后会出现性能下降,可以使用-XX:+UseBiasedLocking
手动开启。
ZGC:可伸缩低延迟垃圾收集器(JEP 377)
ZGC 是在 Java11 引入的(参见 Java11 新特性),一直处于试验阶段,想要体验,需要在参数中使用-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
组合启用,在 Java15 中,ZGC 成为正式特性,想要使用可以直接用命令-XX:+UseZGC
就行。
ZGC 是一个重新设计的并发的垃圾回收器,可以极大的提升 GC 的性能,支持任意堆大小而保持稳定的低延迟。从 https://openjdk.java.net/jeps/333 给出的数据可以看出来,在 128G 堆大小的测试中,ZGC 优势明显,找了一张网上的图片:
虽然 ZGC 愿景很好,但是还有很长的路要走,所以默认的垃圾收集器还是 G1。
Shenandoah:低暂停时间垃圾收集器(JEP 379)
Shenandoah 是在 Java12 引入的(参见)Java12 的新特性,本次和 ZGC 一起转正。同样的,想要使用 Shenandoah,不再需要参数-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
组合,只使用-XX:+UseShenandoahGC
即可。需要注意的是,Shenandoah 只在 OpenJDK 中提供,OracleJDK 中并不包含。
文本块(JEP 378)
文本块是千呼万唤终于转正,在 Java13 中首次引入(参见 Java13 的新特性),在 Java14 中又增加了预览特性(参见 Java14 的新特性),终于在 Java15 确定下来,可以放心使用了。
我们再复习一下:
@Test
void testTextBlock() {
final String singleLine = "你好,我是看山,公众号「看山的小屋」。这行没有换行,而且我的后面多了一个空格 \n 这次换行了";
final String textBlockSingleLine = """
你好,我是看山,公众号「看山的小屋」。\
这行没有换行,而且我的后面多了一个空格、s
这次换行了""";
Assertions.assertEquals(singleLine, textBlockSingleLine);
}
这个功能特性是代码可读性的优化。
预览
密封类和接口(JEP 360)
目前,Java 没有提供对继承的细粒度控制,只有 public、protected、private、包内控制四种非常粗粒度的控制方式。
为此,密封类的目标是允许单个类声明哪些类型可以用作其子类型。这也适用于接口,并确定哪些类型可以实现它们。该功能特性新增了sealed
和non-sealed
修饰符和permits
关键字。
我们可以做如下定义:
public sealed class Person permits Student, Worker, Teacher {}
public sealed class Student extends Person
permits Pupil, JuniorSchoolStudent, HighSchoolStudent, CollegeStudent, GraduateStudent {}
public final class Pupil extends Student {}
public non-sealed class Worker extends Person {}
public class OtherClass extends Worker {}
public final class Teacher extends Person {}
我们可以先定义一个sealed
修饰的类Person
,使用permits
指定被继承的子类,这些子类必须是使用final
或sealed
或non-sealed
修饰的类。其中Student
是使用sealed
修饰,所以也需要使用permits
指定被继承的子类。Worker
类使用non-sealed
修饰,成为普通类,其他类都可以继承它。Teacher
使用final
修饰,不可再被继承。
从类图上看没有太多区别:
但是从功能特性上,起到了很好的约束作用,我们可以放心大胆的定义可以公开使用,但又不想被非特定类继承的类了。
instanceof 模式匹配-预览第二版(JEP 375)
instanceof 模式匹配首先在 Java14 中提供预览功能(参见 Java14 特性),可以提供instanceof
更加简洁高效的实现,在 Java15 中没有新增特性,主要是为了再次收集反馈,根据结果看,大家还是很期待这个功能,在 Java16 中正式提供。
我们再简单看下instanceof
的改进:
@Test
void test1() {
final Object obj1 = "Hello, World!";
int result = 0;
if (obj1 instanceof String str) {
result = str.length();
} else if (obj1 instanceof Number num) {
result = num.intValue();
}
Assertions.assertEquals(13, result);
}
Record 类型-预览第二版(JEP 384)
Record 类型用来增强 Java 语言特性,充当不可变数据载体。在 Java14 中提供预览功能(参见 Java14 新特性),在 Java15 中提供第二次预览,这次预览的目标是收集用户反馈。
比如,我们定义一个Person
类:
public record Person(String name, String address) {
}
我们转换为之前的定义会是一坨下面这种代码:
public final class PersonBefore14 {
private final String name;
private final String address;
public PersonBefore14(String name, String address) {
this.name = name;
this.address = address;
}
public String name() {
return name;
}
public String address() {
return address;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PersonBefore14 that = (PersonBefore14) o;
return Objects.equals(name, that.name) && Objects.equals(address, that.address);
}
@Override
public int hashCode() {
return Objects.hash(name, address);
}
@Override
public String toString() {
return "PersonBefore14{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Record 类型特性有四个特性:
- 设计一个面向对象的结构,表达简单的信息聚合;
- 帮助开发人员专注于建模不可变数据,而不是可扩展的行为;
- 自动实现数据驱动的方法,比如
equals
、getter
、setter
等方法; - 保留长期存在的 Java 规范,比如迁移兼容性。
我们不能将 Record 类型简单的理解为去除“样板化”代码的功能,它不是解决 JavaBean 命名约定的中很多模板化方法的冗余繁杂问题,它的目标不是类似 Lombok 等工具自动生成代码的功能,是从开发人员专注模型的角度出发的。
孵化
外部存储器访问 API-孵化第二版(JEP 383)
外部存储器访问 API 在 Java14 开始孵化(参见 Java14 新特性),在 Java15 中继续孵化状态,这个版本中增加了几个特性:
- 新的
VarHandle
API,用于定制内存访问句柄; - 支持
Spliterator
接口实现并行处理内存段; - 增强了对映射内存段的支持;
- 能够像本机调用一样操作或间接操作内存地址。
外部内存通常是说那些独立 JVM 之外的内存区域,可以不受 JVM 垃圾收集的影响,通常能够处理较大的内存。
这些新的 API 虽然不会直接影响多数的应用类开发人员,但是他们可以在内存的第三方库中提供支持,包括分布式缓存、非结构化文档存储、大型字节缓冲区、内存映射文件等。
其他
移除 Nashorn JavaScript 引擎(JEP 372)
Nashorn JavaScript 引擎最初在 Java8 中引入(参见 Java8 新特性),在 Java11 被标记为过期,在 Java15 中被删除,包括 Nashorn JavaScript 引擎、API、jjs 工具等内容。
Nashorn JavaScript 引擎是一个 JavaScript 脚本引擎,用来取代 Rhino 脚本引擎,对 ECMAScript-262 5.1 有完整的支持,增强了 Java 和 JavaScript 的兼容性,而且有很强的性能。
随着 GraalVM 和其他虚拟机技术最近的引入,Nashorn 引擎不再在 JDK 生态系统中占有一席之地。而且,ECMAScript 脚本语言结构、API 改变速度太快,Nashorn JavaScript 引擎维护成本太高,所以,直接删了。
移除 Solaris 和 SPARC 端口 API(JEP 381)
Solaris 和 SPARC 都已被 Linux 操作系统和英特尔处理器取代。放弃对 Solaris 和 SPARC 端口的支持将使 OpenJDK 社区的贡献者能够加速开发新功能,从而推动平台向前发展。
Solaris 和 SPARC 端口 API 在 Java14 中标记过时,在 Java15 中彻底移除。仅仅半年就痛下杀手,可见社区对于维护这些 API 深受折磨。
废除 RMI Activation(JEP 385)
RMI Activation 在 Java15 中标记为废除,会在未来版本删除。之所以被删除,是因为在现代的 web 应用中,已经不需要这种激活机制,继续维护,增加了 Java 开发人员的维护负担。在 Java8 的时候,已经将其设置为非必选项。
从开发系统的角度看,虽然 RMI Activation 是一个还不错的设计,但是已经有其他替代方案,继续维护开发下去,成本收益完全不匹配,及早舍弃,可以选择更加优秀的方案。有些类似于零边际成本的思想。
文末总结
本文介绍了 Java15 新增的特性,完整的特性清单可以从 https://openjdk.java.net/projects/jdk/15/ 查看。后续内容会发布在 从小工到专家的 Java 进阶之旅 系列专栏中。
青山不改,绿水长流,我们下次见。
推荐阅读
- 从小工到专家的 Java 进阶之旅
- 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
- 一文掌握 Java8 的 Optional 的 6 种操作
- 使用 Lambda 表达式实现超强的排序功能
- Java8 的时间库(1):介绍 Java8 中的时间类及常用 API
- Java8 的时间库(2):Date 与 LocalDate 或 LocalDateTime 互相转换
- Java8 的时间库(3):开始使用 Java8 中的时间类
- Java8 的时间库(4):检查日期字符串是否合法
- Java8 的新特性
- Java9 的新特性
- Java10 的新特性
- Java11 中基于嵌套关系的访问控制优化
- Java11 的新特性
- Java12 的新特性
- Java13 的新特性
- Java14 的新特性
- Java15 的新特性
- Java16 的新特性
- Java17 的新特性
- Java18 的新特性
- Java19 的新特性
- Java20 的新特性
- Java21 的新特性
- Java22 的新特性
- Java23 的新特性
- Java24 的新特性
你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。我还整理了一些精品学习资料,关注公众号「看山的小屋」,回复“资料”即可获得。
个人主页:https://www.howardliu.cn
个人博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java15 的新特性
CSDN 主页:https://kanshan.blog.csdn.net/
CSDN 博文:Java 每半年就会更新一次新特性,再不掌握就要落伍了:Java15 的新特性