概述
JDK 13 于 2019 年 9 月 17 日正式发布。。
JEP(Java Enhancement Proposal)Java增强提案
变动说明
官网:
更多参考:
docs.oracle.com/en/java/jav… 更多版本:docs.oracle.com/en/java/jav…
重要变更和信息
JDK 13 总共包含 5 个新的 JEP ,分别为:
- JEP 350: Dynamic CDS Archives
- JEP 351: ZGC: Uncommit Unused Memory
- JEP 353: Reimplement the Legacy Socket API
- JEP 354: Switch Expressions (Preview) Switch表达式(预览)
- JEP 355: Text Blocks (Preview) 文本块(预览)
Java SE 13引入了文本块,这是不需要转义的多行字符串文字;请参阅《程序员文本块指南》。它还引入了switch表达式的一个更改:使用新的yield语句而不是break语句返回指定的值。但是这两个特性还处于预览阶段。
下载地址
您可以从这个链接下载生产就绪的OpenJDK版本。文件为压缩包,解压并设置环境变量就可以使用。
当然你也可以从这个链接下载Oracle JDK版本(但是需要注意商用限制),更多版本下载。
Java13新特性总结
1、JEP 354:增强 Switch 表达式(预览特性)
JEP 354 (tools/javac)
这仍然是一个预览特性。
Switch 表达式是在 Java 12 中首次作为预览特性引入,而在 Java 13 中对 Switch 表达式做了增强改进:在块中引入了 yield 语句来返回值,而不是使用 break。
yield 关键字用于从 switch 表达式的 case 块中返回一个值。这对于复杂的 Switch 语句特别有用,其中每个 case 块包含多行代码,并且最终需要返回一个值。
Switch 进化:
| java版本 | JEP | 特性 |
|---|---|---|
| Java 5 | 首次引入,仅支持整型数据类型(如 byte, short, char, 和 int)及枚举类型 | |
| Java 7 | 支持 String 类型 | |
| Java 12 | JEP 325 | 预览特性:支持Switch表达式(箭头函数) |
| Java 13 | JEP 354 | 预览特性:加入 yield 语句来替代 break 语句,用于从 switch 表达式返回值 |
在Java 12 中引入Switch表达式作为预览特性,使其既可以用作语句也可以用作表达式。使用switch作为表达式通常会导致代码更加简洁和易读。语句和表达式形式都可以使用传统的case … :标签(冒号,存在下落(Fall-through)行为)或简化case … ->标签(箭头,不存在下落(Fall-through)行为)。此外,两种形式都可以在一种情况下打开多个常量。
为了进一步扩展Switch的功能,Java 13引入yield关键字来处理多分支结构中的返回值。
yield用于在Switch表达式的每个分支中返回一个值,这点与传统的Switch语句需要通过变量赋值来返回值不同。同时在使用->的情况下,如果分支逻辑比较复杂,需要多条语句来处理,那么可以在这些语句的最后使用yield来返回最终的结果。
在Java 12中,虽然他允许直接从Switch表达式中返回值,但在处理复杂的逻辑时,仍需依赖外部变量来返回结果。比如下面这段简单的逻辑:
public static String getTypeOfDay(String day) {
String typeOfDay = switch (day) {
case "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" -> "Weekday";
case "SATURDAY", "SUNDAY" -> "Weekend";
default -> "Unknown";
};
return typeOfDay;
}
对于这简单的逻辑 ,Java 12 可以直接返回,但是这里我想在 default 处增加一个判断 name是否为空的逻辑,这时使用 -> 就无法达到目的了,我们需要定义一个外部变量来处理:
public static String getTypeOfDay2(String name) {
String desp; //外部变量
switch (name) {
case "稻" -> desp = "俗称水稻、大米";
case "黍" -> desp = "shǔ,俗称黄米";
case "稷" -> desp = "jì,又称粟,俗称小米";
case "麦" -> desp = "俗称小麦";
case "菽", "豆" -> desp = "shū,俗称大豆";
default -> {
// 处理复杂逻辑
if (name == null || name.isEmpty()) {
desp = "day is empty";
} else {
throw new IllegalStateException("不是五谷之一: " + name);
}
}
}
return desp;
}
@Test(priority = 0) // 不指定顺序时默认按字母顺序执行
public void test() {
String name = "稷";
System.out.printf("%s:%s%n", name, getTypeOfDay2(name));
}
但是,在 Java 13 中,我们可以使用 yield 在Switch表达式中直接返回:
public static String getTypeOfDay1(String name) {
return switch (name) {
case "稻" -> "俗称水稻、大米";
case "黍" -> "shǔ,俗称黄米";
case "稷" -> "jì,又称粟,俗称小米";
case "麦" -> "俗称小麦";
case "菽", "豆" -> "shū,俗称大豆";
default -> {
// 处理复杂逻辑
if (name == null || name.isEmpty()) {
yield "day is empty";
} else {
throw new IllegalStateException("不是五谷之一: " + name);
}
}
};
}
新的写法显然要简洁多了,好像一直在这么说呀!
2、JEP 355:文本块(预览特性)
JEP 355 (tools/javac)
这是一个预览特性。
将文本块添加到Java语言中。文本块是一个多行字符串文字,它避免了大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。这是JDK13中的预览语言特性。
我们以前从外部copy一段文本串到Java中,会被自动转义,如有一段以下字符串:
<html>
<body>
<p>天地玄黄,宇宙洪荒</p>
</body>
</html>
将其复制到Java的字符串中,会展示成以下内容:
"<html>\n"
" <body>\n"
" <p>天地玄黄,宇宙洪荒</p>\n"
" </body>\n"
"</html>\n";
即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,可以使用文本块定义。文本块是通过三个双引号(""")来定义的,这标志着字符串的开始和结束。在这两个标志之间的所有文本,包括换行符和空格,都将被包含在字符串中。
示例代码:
@Test
public void test() {
// 旧写法
String html = "<html>\n" + //
" <body>\n" + //
" <p>天地玄黄,宇宙洪荒</p>\n" + //
" </body>\n" + //
"</html>";
System.out.println(html);
// 新写法。
String html1 = """
<html>
<body>
<p>天地玄黄,宇宙洪荒</p>
</body>
</html>
""";
System.out.println(html1);
}
执行结果:
<html>
<body>
<p>天地玄黄,宇宙洪荒</p>
</body>
</html>
<html>
<body>
<p>天地玄黄,宇宙洪荒</p>
</body>
</html>
2者输出结果是一样的。但是显然新写法写起来也简单,也更易读。
SQL语句也可以使用:
String query = """
SELECT `id`, `name` FROM `users`
WHERE `sex` = 1
ORDER BY `age`, `create_time`;
""";
文本块可以用来定义一段格式化文本,而不需要想原来每一行都需要增加换行处理。
使用文本块好处:
- 多行字符串的简化:在之前的java中编写多行字符串时,只能需要通过使用
\n来实现换行,通过+来连接多个字符串。文本块则只需要将文本通过三个双引号(""")包裹起来。 - 格式化和缩进处理:采用字符串拼接的方式,是无法格式化和缩进处理的,而使用文本块则会自动处理字符串的格式化和缩进。
- 无需处理特殊字符:在之前的java中对于特殊字符需要进行转义处理,但使用文本块后,则不需要。
3、JEP 350:动态CDS归档
JEP 350 (hotspot/runtime)
这一特性是在JEP310[Java 10新特性]:Application Class-Data Sharing基础上扩展而来的,CDS指的就是Class-Data Sharing。
那么,这个JEP310是个啥东西呢?
我们知道在同一个物理机/虚拟机上启动 多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以Java团队引入了CDS的概念,通过把一些 核心类在 每个JVM间共享,每个JVM只需要装载自己的应用类,启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。
CDS 只能作用于 Boot Class Loader 加载的类,不能作用于 App Class Loader 或者自定义的 Class Loader 加载的类。
在 Java 10 中,则将 CDS 扩展为 AppCDS,顾名思义,AppCDS 不止能够作用于 Boot Class Loader了,App Class Loader 和自定义的 Class Loader 也都能够起作用,大大加大了 CDS 的适用范围。
也就说开发自定义的类也可以装载给多个JVM共享了。
Java 10中包含的JEP310的通过跨不同Java进程共享公共类元数据来减少了内存占用和改进了启动时间。
但是,JEP310中,使用AppCDS的过程还是比较复杂的,需要有三个步骤:
- 决定要 Dump 哪些 Class
- 将类的内存 Dump 到归档文件中
- 使用 Dump 出来的归档文件加快应用启动速度
这一次的JDK 13中的JEP 350 ,在JEP310的基础上,又做了一些扩展。
JEP 350扩展应用程序类数据共享(AppCDS),以允许在Java应用程序退出时动态归档类,归档类将包括默认的基础层 CDS(class data-sharing)存档中不存在的所有已加载的应用程序类和库类。。它还通过消除用户进行试运行以为每个应用程序创建类列表的需要来提高AppCDS的可用性。-Xshare:dump选项使用类列表启用的现有静态归档将继续按原样工作。简单说,就是,在Java 13中再使用AppCDS的时候,不需要原来那么复杂了。
动态生成的归档文件是在与运行的JDK映像打包的默认系统归档文件之上创建的。为每个应用程序生成一个单独的顶层归档文件。用户可以指定动态存档名称的文件名作为-XX:ArchiveClassesAtExit选项的参数。例如,以下命令创建hello.jsa:
~$ bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
要使用此动态存档运行同一应用程序,请执行以下操作:
~$ bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello
用户还可以在-XX:SharedArchiveFile选项中指定基本归档和动态归档,例如:
-XX:SharedArchiveFile=<base archive>:<dynamic archive>
JDK-8221706 提供了有关命令行选项的更多详细信息。
4、JEP 351:增强 ZGC 释放未使用内存
JEP 351 (hotspot/gc)
GC后的内存如何处置,其实是取决于不同的垃圾回收器的。因为把内存还给OS,意味着要调整JVM的堆大小,这个过程是比较耗费资源的。
在JDK 11中,Java引入了 ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的。并且,ZGC释放的内存是不会还给操作系统的。
而在Java 13中,ZGC经过增强,可以将未使用的堆内存返回到操作系统。这对于关注内存占用的应用程序和环境非常有用。
此特性在默认情况下是启用的,但可以使用-XX:-ZUncommit显式禁用。此外,内存不会未提交,因此堆大小将缩小到最小堆大小(-Xms)以下。这意味着,如果最小堆大小(-Xms)配置为等于最大堆大小(-Xmx),则此特性将被隐式禁用。
可以使用-XX:ZUncommitDelay=<seconds>(默认为300秒)配置取消提交延迟。此延迟指定了内存在符合取消限制条件之前应该未使用的时间。
5、JEP 353:重构 Socket API
JEP 353 (core-libs/java.net)
java.net.Socket和java.net.ServerSocket的API底层实现在此版本中已被替换。
默认使用新的API,但是旧的实现(称为PlainSocketImpl或Plain实现)并没有删除,可以继续运行。可以通过增加系统属性jdk.net.usePlainSocketImpl或将其值设为true来使用旧实现,即使用-Djdk.net.usePlasinSocketImpl或-Djdk.net.usePlainSocketImpl=true=来运行。该属性也可以在JDK网络配置文件中进行配置,该文件位于${java.home}/conf/net.properties中。旧实现以及用于选择旧实现的系统属性将在将来的版本中删除。
新的API使用全新实现的 NioSocketImpl 来替换原来的PlainSocketImpl。可以查看java.net.SocketImpl的源代码
package java.net;
……
/**
* The abstract class {@code SocketImpl} is a common superclass
* of all classes that actually implement sockets. It is used to
* create both client and server sockets.
*
……
* removed in a future version.
*
* @author unascribed
* @since 1.0
*/
public abstract class SocketImpl implements SocketOptions {
private static final boolean USE_PLAINSOCKETIMPL = usePlainSocketImpl();
/**
* Creates an instance of platform's SocketImpl
*/
@SuppressWarnings("unchecked")
static <S extends SocketImpl & PlatformSocketImpl> S createPlatformSocketImpl(boolean server) {
if (USE_PLAINSOCKETIMPL) {
return (S) new PlainSocketImpl(server); // 旧
} else {
return (S) new NioSocketImpl(server); //新
}
}
……
}
6、移除的APIs、工具、容器
参考:
Oracle JDK和OpenJDK之间的差异
尽管官方已经声明了让OpenJDK和Oracle JDK二进制文件尽可能接近的目标,但至少对于JDK 13来说,这两个选项之间仍然存在一些差异。
目前的差异是:
Oracle JDK提供了安装程序(msi、rpm、deb等),它们不仅将JDK二进制文件放置在系统中,还包含更新规则,在某些情况下还可以处理一些常见的配置,如设置常见的环境变量(如Windows中的JAVA_HOME)和建立文件关联(如使用JAVA启动.jar文件)。OpenJDK仅作为压缩档案(tar.gz或.zip)提供。- 在版本号为
9和10下,javac —release的行为会有所不同。Oracle JDK的二进制文件中包含了一些没有加入到OpenJDK二进制文件中的 API,比如javafx、资源管理以及(在 JDK 11 变更之前)JFR APIs。 Usage Logging仅在Oracle JDK中可用。Oracle JDK要求使用Java加密扩展(JCE(Java Cryptography Extension ))代码签名证书对第三方加密提供程序进行签名。OpenJDK继续允许使用未签名的第三方加密提供程序。java -version命令输出结果不同。Oracle JDK将输出java并包含LTS。Oracle生成的OpenJDK将显示OpenJDK,不包括Oracle特定的LTS标识符。Oracle JDK将在OTN许可证下发布。任何许可证文件都需要指向OTN。OpenJDK将在GPLv2wCP下发布,并将包括GPL许可证。Oracle JDK将在FreeType许可证下分发FreeType,而OpenJDK则将在GPLv2下分发。因此,\legal\java.desktop\freetype.md的内容将有所不同。Oracle JDK有Java cup和steam图标,而OpenJDK有Duke图标。Oracle JDK源代码包括ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.的说明,OpenJDK源代码包括GPL许可条款。