侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 274 篇文章
  • 累计创建 55 个标签
  • 累计收到 74 条评论

Java 15 特性介绍

Z同学
2020-09-29 / 0 评论 / 0 点赞 / 873 阅读 / 13,502 字
温馨提示:
本文最后更新于 2021-11-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

2020 是java诞生的25周年,jdk 15 在2020年9月15日如期发布了。

java 5的泛型(Generics)支持,java 8 的lambda语法,java 9 的Modules

下图为java 各版本的更新图

image.png

下一个java 版本将会在6个月有进行发布,即java 16将于2021年3月份发布。

下面主要介绍java 15的新特性。

更新频率

首先很多人说,java 15 等只是临时版过渡版本,6个月就过期了。但是其实在java 9 开始,java 的更新频率开始保持在6个月一个版本更新的节奏了。

并不是所说的过渡版本等概念。

主要原因在于原先大型主要版本中都是时隔多年后进行发布,每次发布时会添加成千上万的修复程序和大约一百多个jep(jdk 增强建议)。并不利于java的功能改进和bug修复。所以在Oracle公司接手之后,java 的更新速度瞬间就由原先的多年更新提高到了现在的6个月一次的更新节奏。而在6个月一更新的版本上。因为更新原因,也有长时间维护的版本。 主要是两种更新的方案不同而已。

题外话,为什么现在的很多开发却不是使用最新的java ? 那是因为很多设备的本地jdk库版本并没有升级,造成了程序开发保持在低版本上。

例如:android 系统上的java 不支持java15的特性,你的开发按照java15进行开发,最终会发现很多语法或者功能在设备之中无法使用。

Java 15标记为已修复的2136个JIRA的相应组织图表

java 15 的新功能

主要提供了14项功能的增强和修改。

分别是:

  • : Edwards-Curve Digital Signature Algorithm (EdDSA)
  • : Sealed Classes (Preview)
  • : Hidden Classes
  • : Remove the Nashorn JavaScript Engine
  • : Reimplement the Legacy DatagramSocket API
  • : Disable and Deprecate Biased Locking
  • : Pattern Matching for instanceof (Second Preview)
  • : ZGC: A Scalable Low-Latency Garbage Collector
  • : Text Blocks
  • : Shenandoah: A Low-Pause-Time Garbage Collector
  • : Remove the Solaris and SPARC Ports
  • : Foreign-Memory Access API (Second Incubator)
  • : Records (Second Preview)
  • : Deprecate RMI Activation for Removal

其中包括一个孵化器模块,三个预览功能,两个不推荐使用的功能以及两个删除功能。

下面介绍各功能内容

1.Edwards-Curve Digital Signature Algorithm (EdDSA)(爱德华兹曲线数字签名算法)

官网连接:jep:339

EdDSA是一种现代的椭圆曲线签名方案,与JDK中的现有签名方案相比,具有多个优点。此JEP的主要目标是按照RFC 8032的标准实现此方案。此新的签名方案不能替代ECDSA。

与现有和其他签名方案相比,EdDSA具有更高的安全性和性能。已经在OpenSSL 和BoringSSL之中得到支持。该签名方案是TLS 1.3的可选组件之一,并且是TLS1.3仅允许的三种签名方案之一。

API示例如下:

// 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);
byte[] s = sig.sign();

// example: use KeyFactory to contruct a public key
KeyFactory kf = KeyFactory.getInstance("EdDSA");
boolean xOdd = ...
BigInteger y = ...
NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
EdECPublicKeySpec pubSpec = new EdECPublicKeySpec(paramSpec, new EdPoint(xOdd, y));
PublicKey pubKey = kf.generatePublic(pubSpec);

2.Sealed Classes (Preview) (密封类)

官网连接:jep:360

新增sealed 关键字,进行限制类和接口的继承和封装。

通过密封的类和接口增强Java编程语言。密封的类和接口限制可以扩展或实现它们的其他类或接口。

特性:

  • 允许类或接口的作者控制负责实现该代码的代码。
  • 提供比访问修饰符更具声明性的方式来限制父类的使用。
  • 通过对模式进行详尽的分析来支持模式匹配的未来方向。

final 标注的对象,和friend 之类的新形式的对象不能使用。

封闭关键字的原因:

在Java中,类层次结构通过继承实现代码的重用:父类的方法可以被许多子类继承(并因此被重用)。但是,类层次结构的目的并不总是重用代码。有时,其目的是对域中存在的各种可能性进行建模,例如图形库支持的形状类型或金融应用程序支持的贷款类型。当以这种方式使用类层次结构时,限制子类集可以简化建模。

密封class API示例如下:

//案例一 。该类 只允许相同包名下 的Circle,Rectangle,Square 进行继承
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

//案例二,允许其他包名下的类进行继承
package com.example.geometry;

public abstract sealed class Shape 
    permits com.example.polar.Circle,
            com.example.quad.Rectangle,
            com.example.quad.simple.Square {...}

//案例三
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...}

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...}

密封类,对于它的子类(permits 指定的子类)有三个约束:
1.密封类及其允许的子类必须属于同一模块,并且如果在未命名的模块中声明,则必须属于同一包。
2.每个允许的子类都必须直接扩展密封的类。
3.每个允许的子类都必须选择一个修饰符来描述其如何继续其父类发起的密封:
- 可以声明一个允许的子类,final以防止其在类层次结构中的进一步扩展。
- 可以声明一个允许的子类,sealed以允许其层次结构的一部分扩展到超出其密封超类所设想的范围,但以受限的方式。
- 可以声明一个允许的子类,non-sealed以便其层次结构的一部分恢复为未知子类可以扩展的扩展。(密封类不能阻止其允许的子类这样做。)

案例介绍:

package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square {...}

public final class Circle extends Shape {...}

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}

public non-sealed class Square extends Shape {...}

密封interface api 示例:

//案例一
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}

public final class ConstantExpr implements Expr {...}
public final class PlusExpr     implements Expr {...}
public final class TimesExpr    implements Expr {...}
public final class NegExpr      implements Expr {...}

//案例二
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}

public record ConstantExpr(int i)       implements Expr {...}
public record PlusExpr(Expr a, Expr b)  implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e)           implements Expr {...}

3.Hidden Classes (隐藏类)

官网连接:jep:371

隐藏类不能被其他类的字节码直接使用。隐藏类适用于在运行时生成类并通过反射间接使用它们的框架。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类进行卸载。

  • 允许框架将类定义为框架的不可发现的实现细节,以便它们不能被其他类链接,也不能通过反射来发现。

  • 支持使用不可发现的类扩展访问控制嵌套。

  • 支持主动卸载不可发现的类,因此框架可以灵活地定义所需数量。

  • 弃用非标准API sun.misc.Unsafe::defineAnonymousClass,以弃用非标准API以便在将来的版本中将其删除。

  • 请勿以任何方式更改Java编程语言。

基于JVM构建的许多语言实现都依靠动态类生成来提高灵活性和效率。例如,在Java语言的情况下,javac不会class在编译时将lambda表达式转换为专用文件,而是发出字节码,该字节码可动态生成并实例化一个类,以在需要时产生与lambda表达式相对应的对象。同样,非Java语言的运行时通常通过使用动态代理来实现那些语言的高级功能,该代理也可以动态生成类。

开发者通常希望动态生成的类在逻辑上成为静态生成的类的实现的一部分。此意图表明动态生成的类所需的各种属性。

不幸的是,定义类的标准APIClassLoader::defineClass和Lookup::defineClass都无视该类的字节码是动态(在运行时)还是静态(在编译时)生成的。这些API始终定义一个可见的类,该类将在每次在同一加载器层次结构中的另一个类尝试链接该名称的类时使用。因此,该类可能比所需的类更容易发现或具有更长的生命周期。另外,如果嵌套的宿主类事先知道成员类的名称,则API只能定义一个将充当嵌套成员的类。实际上,这可以防止动态生成的类成为嵌套成员。

将提高所有基于JVM的语言实现的效率。

4.Remove the Nashorn JavaScript Engine (删除Nashorn JavaScript引擎)

官网连接:jep:372

删除Nashorn JavaScript脚本引擎和API,以及该jjs 工具。

该工具是JDK 8之中被集成的。用于替代Rhino脚本引擎。它是基于ECMAscript-262 5.1标准进行的完整实现,但是随着ECMAScript语音的构造以及该API的快速更新与迭代。openJDK难以维护Nashorn库了。所以,删除了。

不影响javax.acriptAPI.

主要删除:
下面两个JDK模块将永久删除:

1.jdk.scripting.nashorn
包括:jdk.nashorn.api.scripting
jdk.nashorn.api.tree.

2.jdk.scripting.nashorn.shell
包括:jjs tool.

5.Reimplement the Legacy DatagramSocket API(重新实现旧版DatagramSocket API)

官网连接:jep:373

用易于维护和调试的更简单,更现代的实现来代替java.net.DatagramSocket和java.net.MulticastSocketAPI的基础实现。新的实现将很容易适应虚拟线程的工作,当前正在Project Loom中进行探索。这是JEP 353的后续产品,该产品已经重新实现了传统的Socket API。

原因:
java.net.DatagramSocket和java.net.MulticastSocketAPI的代码库及其基础实现很旧且脆弱:

  • 实现可以追溯到JDK 1.0。它们是难以维护和调试的遗留Java和C代码的混合。

  • MulticastSocket的实现尤其成问题,因为它可以追溯到IPv6仍处于开发阶段。许多基础本机实现都尝试以难以维护的方式协调IPv4和IPv6。

  • 该实现还存在一些并发问题(例如,异步关闭),需要进行大修才能正确解决。

此外,在驻留而不是阻塞系统调用中底层内核线程的虚拟线程的情况下,当前实现不适合此目的。随着基于数据报的传输再次获得牵引力(例如 QUIC),需要更简单,更可维护的实现。

image.png

默认情况下启用新的实现。通过直接使用选择器提供程序(sun.nio.ch.SelectorProviderImpl和sun.nio.ch.DatagramChannelImpl)的平台默认实现,它为数据报和多播套接字提供了不间断的行为。因此,安装自定义选择器提供程序将对DatagramSocket和 无效MulticastSocket。

6.Disable and Deprecate Biased Locking(禁用和弃用偏向锁定)

官网连接:jep:374

偏向锁定是HotSpot虚拟机中使用的一种优化技术,可以减少无竞争锁定的开销。它的目的是通过假定监视器一直归给定线程拥有,直到另一个线程尝试获取它为止,从而避免在获取监视器时执行比较交换原子操作。监视器的初始锁定使监视器偏向该线程,从而避免了对同一对象进行后续同步操作时需要原子指令。当许多线程对以单线程方式使用的对象执行许多同步操作时,从历史上看,对锁施加偏向已导致与常规锁定技术相比,性能得到了显着改善。

过去看到的性能提升在今天已经不那么明显了许多从偏向锁定中受益的应用程序都是使用早期Java集合API的较旧的旧版应用程序,这些API在每次访问(例如Hashtable和Vector)上同步。较新的应用程序通常使用非同步集合(例如,HashMap和ArrayList)(在Java 1.2中针对单线程方案引入)或性能更高的并发数据结构(在Java 5中针对多线程方案引入)。这意味着,如果代码更新为使用这些较新的类,则由于不必要的同步而受益于偏向锁定的应用程序可能会看到性能提高。此外,围绕线程池队列和工作线程构建的应用程序通常在禁用偏置锁定的情况下性能更好。

在JDK 15之前,始终启用并提供偏置锁定。使用此JEP,除非-XX:+UseBiasedLocking在命令行上设置,否则在启动HotSpot时将不再启用偏置锁定。

我们将不赞成该UseBiasedLocking选项以及与偏置锁定的配置和使用有关的所有选项。

产品选择:BiasedLockingStartupDelay,BiasedLockingBulkRebiasThreshold,BiasedLockingBulkRevokeThreshold,BiasedLockingDecayTime和UseOptoBiasInlining
诊断选项:PrintBiasedLockingStatistics和PrintPreciseBiasedLockingStatistics
这些选项仍将被接受并执行,但是将发出弃用警告。

7.Pattern Matching for instanceof (Second Preview) (用于instanceof的模式匹配 第二次预览版)

官网连接:jep:375

该功能在2017年提出,2019年末在jdk 14上作为预览功能发布。

这次是作为第二次预览版发布,没有做修改,只是继续收集用户反馈,可能后续会有其他的修改吧。

原先api 使用方法:

if (obj instanceof String) {
    String s = (String) obj;
    // use s
}

在这个方法之中, 会有三个步骤:1. 判断 obj--String? 。2 转换 obj转String,3.变量声明 创建s变量。

这种不是一种最优解决方案。

而使用模式匹配,可以减少instanceof 的显示强制转换的数量并提高效率。

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

未来的JEP将通过与其他语言构造(例如switch表达式和语句)进行模式匹配来增强Java编程语言。

8.ZGC: A Scalable Low-Latency Garbage Collector (可扩展的低延迟垃圾收集器 ZGC)

官网连接:jep:377

ZGC是在JDK11 的时候集成进入的,但是当初出于实验功能版本,现在已经转为正式功能。但是该jep不会更改默认的GC回收,默认还是使用G1进行回收

而ZGC主要是通过减少GC停顿时间来提高性能。
当前几个月没有针对ZGC的新的bug,表面了当前ZGC的稳定性。所以在java 15由实验版改为了正式版。

可以通过-XX:+UnlockExperimentalVMOptions -XX:+UseZGC命令行选项启用ZGC。

ZGC的改进加强:
1.并发类卸载。
2.取消提交未使用的内存。
3.最大堆大小从4TB增加到16TB
4.最小堆大小减少到8MB
5.-XX:SoftMaxHeapSize
6.支持JFR泄漏分析器
7.支持班级数据共享
8.有限和不连续的地址空间
9.支持将堆放在NVRAM上
10.增强NUMA意识
11.多线程堆预触

9.Text Blocks (文本块)

官网连接:jep:378

将文本块添加到Java语言。文本块是多行字符串文字,它避免了大多数转义序列的需要,以一种可预测的方式自动设置字符串的格式,并在需要时使开发人员可以控制格式。

最初在2019年年初提出了文本块概念,计划在jdk 12时进行发布,但是最终被撤回未能进行发布。

于2019年6月在jdk 13之中作为预览版进行了发布。针对jdk 13的反馈,在jdk14进行了优化,并发布了第二版预览版。

针对在上述两个预览版本之中的反馈和修改。

并与jdk 15 也就是当前版本,完成了当前最终版本和永久版本。后续不在进行进行一步的更改了。可以一直使用。

功能:

  • 通过简化表示跨越几行源代码的字符串的工作,简化了编写Java程序的任务,同时避免了常见情况下的转义序列。

  • 增强Java程序中表示用非Java语言编写的代码的字符串的可读性。

  • 通过规定任何新构造都可以表示与字符串文字相同的字符串集,解释相同的转义序列并以与字符串文字相同的方式进行操作,来支持从字符串文字的迁移。

  • 添加转义序列以管理显式空白和换行控件。

API使用示例:

//案例一
//java 13 以前的使用方法
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";

//java 13以后的使用方法

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;

//案例二
//原方法
String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
               "WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
               "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";
//新方法
String query = """
               SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
               WHERE "CITY" = 'INDIANAPOLIS'
               ORDER BY "EMP_ID", "LAST_NAME";
               """;

//案例三
//原方法
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
                         "    print('\"Hello, world\"');\n" +
                         "}\n" +
                         "\n" +
                         "hello();\n");
//新方法
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                         function hello() {
                             print('"Hello, world"');
                         }
                         
                         hello();
                         """);

与字符串文字不一样的地方就在于 “\”和“\n” 字符,文本快也支持该字符,但是因为没有必要,文本块直接支持换行。

例如:

"""
line 1
line 2
line 3
"""
//等效于下面
"line 1\nline 2\nline 3\n"

10.Shenandoah: A Low-Pause-Time Garbage Collector(一种低暂停时间的垃圾收集器)

官网连接:jep:379

Shenandoah 垃圾回收算法,从实验功能转为了正式功能。

该算法是jdk 12时引入的回收算法。通过与正在运行的java线程同时进行疏散工作减少GC暂停时间。

11.Remove the Solaris and SPARC Ports(删除Solaris和SPARC端口)

官网连接:jep:381

  • 删除所有特定于Solaris操作系统的源代码
  • 删除所有特定于SPARC体系结构的源代码
  • 更新文档和源代码注释以用于将来的版本

官方给出的原因:
当前正在开发的许多项目和功能(如Valhalla,Loom和Panama)都需要对CPU体系结构和特定于操作系统的代码进行重大更改。放弃对Solaris和SPARC端口的支持将使OpenJDK社区中的贡献者能够加速新功能的开发,这将推动平台向前发展。

删除目标:
src/hotspot/cpu/sparc src/hotspot/os/solaris src/hotspot/os_cpu/solaris src/hotspot/os_cpu/linux_sparc src/hotspot/os_cpu/solaris_x86 src/java.base/solaris src/java.desktop/solaris src/jdk.attach/solaris src/jdk.crypto.cryptoki/solaris src/jdk.crypto.ucrypto/solaris src/jdk.management/solaris src/jdk.net/solaris
删除或调整由以下预处理程序定义和宏保护的C / C ++代码:
SPARC,__sparc__,__sparc,__sparcv9 SOLARIS, __solaris__ SPARC_ONLY, NOT_SPARC SOLARIS_ONLY, NOT_SOLARIS SOLARIS_MUTATOR_LIBTHREAD SPARC_WORKS
删除或调整检查Solaris或SunOS的Java代码,例如:
System.getProperty(“os.name”).contains(“Solaris”) System.getProperty(“os.name”).startsWith("SunOS")
删除特定于Solaris的功能:
模块中的OracleUcrypto提供者jdk.crypto.ucrypto(8234870) 该jdk.net.SocketFlow 套接字选项中jdk.net模块(8234871)
删除或调整与Solaris,SPARC或Oracle Studio有关的构建系统(自动制作等)逻辑;具体来说,以下变量和值:
OPENJDK_{BUILD,TARGET}_OS = Solaris OPENJDK_{BUILD,TARGET}_CPU_ARCH = sparc TOOLCHAIN_TYPE = solstudio is{Build,Target}Os = solaris is{Build,Target}Cpu = sparcv9
删除或调整仅与Solaris或SPARC相关或仅在Solaris或SPARC上执行的测试,例如:
jtreg 测试使用 @requires os.family == "solaris" @requires os.arch == "sparc" @requires os.arch == "sparcv9" @requires (vm.simpleArch == "sparcv9")
该Platform.isSolaris()或Platform.isSparc()测试库方法,而方法本身
清理问题列表以除去对solaris或SPARC的任何引用
谨慎地调整参考Solaris或SPARC的源代码中的注释
在许多情况下,只需删除注释即可,但是即使删除了端口,对Solaris和SPARC的某些引用仍然可能有用
删除Solaris devkit创建者脚本(在下方make/devkit)
在JIB配置文件中删除任何特定于Solaris或SPARC的逻辑

12.Foreign-Memory Access API (Second Incubator) (外部存储器访问API 第二次孵化)

官网连接:jep:383

该API允许Java程序安全有效地访问Java堆之外的外部内存。

外部存储器访问API在jdk 14版本作为孵化特性。本次版本更新后作为第二次孵化特性进行发布。

特性:

  • 通用性:单个API应该能够在各种类型的外部内存(例如,本机内存,持久性内存,托管堆内存等)上运行。
  • 安全性:无论操作哪种内存,API都不可能破坏JVM的安全性。
  • 确定性:对外部存储器的解除分配操作应在源代码中明确。
  • 可用性:对于需要访问外部内存的程序,该API应该是诸如的传统Java API的引人注目的替代品sun.misc.Unsafe。

13.Records (Second Preview) (记录,二次预览)

官网连接:jep:384

Records 也是第二次出现的预览功能,在jdk 14 首次出现。本次进行更新后再次作为预览功能发布。

特性:

  • 设计一个表达简单值集合的面向对象的构造。
  • 帮助程序员专注于对不可变数据进行建模,而不是对可扩展行为进行建模。
  • 自动实现数据驱动的方法,例如equals和访问器。
  • 保留长期的Java原则,例如标称类型和迁移兼容性。
//原写法
class Point {
    private final int x;
    private final int y;

    Point(int x, int y) { 
        this.x = x;
        this.y = y;
    }

    int x() { return x; }
    int y() { return y; }

    public boolean equals(Object o) { 
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return other.x == x && other.y = y;
    }

    public int hashCode() {
        return Objects.hash(x, y);
    }

    public String toString() { 
        return String.format("Point[x=%d, y=%d]", x, y);
    }
}
//record 新写法, 意思与上面的一样。写在括号里面的参数会自动生成privata final 修饰符,以及public 访问方法,同时也会生成equals 和hashCode 与toString方法

record Point(int x, int y) { }

14.Deprecate RMI Activation for Removal (放弃RMI 激活并且删除)

官网连接:jep:385

弃用RMI激活机制以便将来删除。RMI激活是RMI的过时部分,自Java 8开始,RMI一直是可选的。不会弃用RMI的其他部分。

请注意不是删除RMI,而是RMI Activation 模块将会在之后版本迭代之中被删除,当前只是设置为废弃标注。

官方删除原因
1.RMI Activation 实际上已经过时很就了。
2.RMI Activation 功能使用量过少。
3.RMI Activation 带来了持续的维护负担。

0

评论区