【转】【译】Java 14 特性及更新

前言

2020年3月17日,Java 14 发布。通过这篇文章我们来看下它新增的一些特性,便于我们更好的了解及掌握。

正文

PS: 预览特性表示改特性被放到该版本后,可以正常使用,但在以后版本可能会有改动/删除(也可能不再改动/删除)。

JEP 305 – instanceof 匹配模式(预览特性)

instanceof操作符的改进

我们以一个例子来看下instanceof操作符的改进。

  1. 旧版本实现

如果应用程序需要处理某种类型的类,但我们有父类类型的引用,那么我们需要检查该实例的类型并进行适当的类型转换。

例如,Customer的类型可以是BusinessCustomerPersonalCustomer。根据客户实例的类型,我们可以根据上下文获取信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Customer customer = new PersonalCustomer();   //通过某些方法拿到数据,略
String customerName = "";

//旧版本实现

if(customer instanceof PersonalCustomer)
{
PersonalCustomer pCustomer = (PersonalCustomer) customer; //强转
customerName = String.join(" ", pCustomer.getFirstName(),
pCustomer.getMiddleName(),
pCustomer.getLastName());
}
else if(customer instanceof BusinessCustomer){
BusinessCustomer bCustomer = (BusinessCustomer) customer; //强转
customerName = bCustomer.getLegalName();
}
  1. 新版本实现

现在,通过与instanceof进行模式匹配,我们可以用以下方式编写类似的代码。在这里,我们可以减少类型转换的样板代码。

1
2
3
4
5
6
7
8
9
10
11
//新版本实现
if(customer instanceof PersonalCustomer pCustomer)
{
customerName = String.join(" ", pCustomer.getFirstName(),
pCustomer.getMiddleName(),
pCustomer.getLastName());
}
else if(customer instanceof BusinessCustomer bCustomer)
{
customerName = bCustomer.getLegalName();
}

相关细节

  1. 类型测试模式

类型测试模式包含以下内容:

  • 可以应用于目标的谓词
  • 只有在谓词成功应用于目标时才从目标提取的一组绑定变量

类型测试模式由指定类型的谓词和单个绑定变量组成。

在下面的代码中,短语字符串s是类型测试模式:

1
2
3
4
5
if (obj instanceof String s) {
// 这儿可以直接使用s变量
} else {
// 不能使用s变量
}
  1. 它做了什么

如果objString的实例,则instanceof操作符将目标obj“匹配”到类型测试模式,然后将其转换为String并分配给绑定变量s

需要注意的是,只有当obj不为空时,模式才会匹配,s才会被赋值。

  1. 复杂表达式用法

if语句变得更加复杂时,绑定变量的作用域也相应增大。

例如,当我们添加&&运算符和另一条语句时,只有instanceof成功并分配给pCustomer时,添加的语句才会被计算。另外,true块中的pCustomer引用了所包含类中的一个字段。

1
2
3
4
5
6
7
8
//可以执行
if(customer instanceof PersonalCustomer pCustomer
&& pCustomer.getId() > 0)
{
customerName = String.join(" ", pCustomer.getFirstName(),
pCustomer.getMiddleName(),
pCustomer.getLastName());
}

与上面的情况相反,当我们添加||操作符和另一条语句时,绑定变量pCustomer不在||操作符右手边的作用域内,也不在true块的作用域内。在这些点上,pCustomer引用了封闭类中的一个字段。

1
2
3
4
5
6
7
8
9
//编译错误 :: The pattern variable pCustomer is not in scope in this location

if(customer instanceof PersonalCustomer pCustomer
|| pCustomer.getId() > 0)
{
customerName = String.join(" ", pCustomer.getFirstName(),
pCustomer.getMiddleName(),
pCustomer.getLastName());
}

JEP 368 – 文本块(二次预览特性)

简介

Java中,文本块是一个多行字符串字面量。这意味着我们不需要陷入显式的行结束符、字符串连接和分隔符的混乱就可以编写普通字符串文本。

Java 文本块在 Java 13 (JEP 355)Java 14 (JEP 368) 中作为预览特性可用。它计划成为Java 15 (JEP 378) 中的一个标准特性。

要启用这个预览特性,我们必须使用-enable-preview-source 14标志。

文本块语法

  • 文本块由多行文本组成,并使用三个双引号字符(“ “ “)作为开始和结束分隔符。
  • 开始的三个双引号字符后面总是跟一个行结束符。
  • 我们不能将分隔符和文本块放在一行上。开始的分隔符必须在它自己的行上。内容只能从下一行开始。
  • 如果文本内容包含单引号或双引号,则不需要对它们进行转义。
1
2
3
4
5
6
7
8
9
String dbSchema =   """
CREATE TABLE 'TEST'.'EMPLOYEE'
(
'ID' INT NOT NULL DEFAULT 0 ,
'FIRST_NAME' VARCHAR(100) NOT NULL ,
'LAST_NAME' VARCHAR(100) NULL ,
'STAT_CD' TINYINT NOT NULL DEFAULT 0
);
""";

看起来十分简单,我们再深入了解一下。

与String相似之处

  • 从文本块生成的实例的类型是java.lang.String。具有与传统双引号字符串相同特征的字符串。这包括对象表示和在字符串池中的表现。
  • 我们可以使用文本块作为String类型的方法参数传递。
  • 文本块可以在任何可以使用字符串文字的地方使用。例如,我们可以将它用于字符串连接。
1
2
3
4
5
6
7
String string = "Hello";
String textBlock = """
World""";

String joinedString = string + textBlock;

System.out.println(joinedString);

输出:

1
2
Hello
World

缩进

  1. 偶然缩进和基本缩进

文本块保留其内容的缩进。为了执行此操作,JEP将空格分为偶然缩进和基本缩进。

让我们来看下第一个例子:

1
2
3
4
5
6
7
8
9
10
11
String dbSchema =   """
CREATE TABLE 'TEST'.'EMPLOYEE'
(
'ID' INT NOT NULL DEFAULT 0 ,
'FIRST_NAME' VARCHAR(100) NOT NULL ,
'LAST_NAME' VARCHAR(100) NULL ,
'STAT_CD' TINYINT NOT NULL DEFAULT 0
);
""";

System.out.println(dbSchema);

输出:

1
2
3
4
5
6
7
|CREATE TABLE 'TEST'.'EMPLOYEE'
|(
| 'ID' INT NOT NULL DEFAULT 0 ,
| 'FIRST_NAME' VARCHAR(100) NOT NULL ,
| 'LAST_NAME' VARCHAR(100) NULL ,
| 'STAT_CD' TINYINT NOT NULL DEFAULT 0
|);

这里,我们有两种类型的缩进:

第一个缩进是从行开始到所有行中的单词“CREATE”为止。这可以根据各种因素增加或减少,比如格式化插件或开发人员的选择。这是偶然的缩进。

第二次缩进是从字符’(‘到’ID’。大部分空间是4到8个空格。这样做是为了保持文本块的缩进意图。这被称为基本缩进。

Java文本块删除所有偶然的缩进,只保留基本的缩进。

  1. 添加自定义的缩进

让我们想象一下,在上面的例子中,我们想要给所有行的左边两个制表符缩进。要做到这一点,我们可以将关闭的三重引号精确地向左移动两个制表符。放置的位置与应起压痕的位置完全相同。

1
2
3
4
5
6
7
8
9
10
11
String dbSchema =   """
CREATE TABLE 'TEST'.'EMPLOYEE'
(
'ID' INT NOT NULL DEFAULT 0 ,
'FIRST_NAME' VARCHAR(100) NOT NULL ,
'LAST_NAME' VARCHAR(100) NULL ,
'STAT_CD' TINYINT NOT NULL DEFAULT 0
);
""";

System.out.println(dbSchema);

输出:

1
2
3
4
5
6
7
|       CREATE TABLE 'TEST'.'EMPLOYEE'
| (
| 'ID' INT NOT NULL DEFAULT 0 ,
| 'FIRST_NAME' VARCHAR(100) NOT NULL ,
| 'LAST_NAME' VARCHAR(100) NULL ,
| 'STAT_CD' TINYINT NOT NULL DEFAULT 0
| );

另外,请注意文本块中每行末尾的空格也会被Java编译器除去。

  1. TAB的处理

对于编译器来说,了解制表符在不同编辑器中是如何显示的并不困难。

编译器将单个空格字符视为单个制表符,即使制表符可能会产生相当于8个空格的空白。

行终止符

不同的平台有不同的行结束符。Java不进行平台检测,并将文本块中的所有行终止符规范化为\n

如果需要平台行终止符,则可以使用String::replaceAll("\n",System.lineSeparator())

1
2
3
4
5
6
7
8
9
String string = "Hello";
String textBlock = """
World""";

String joinedString = string + textBlock;

joinedString = joinedString.replaceAll("\n", System.lineSeparator());

System.out.println(joinedString);

新转义符

  1. 避开换行符

很多时候,我们只希望将内容写入程序中的多行,但它们实际上是单个字符串内容。在这种情况下,我们可以使用行结束符转义字符,即单反斜杠’'。它禁止包含隐式换行字符。

1
2
3
4
5
6
7
8
9
10
11
String dbSchema = """
CREATE TABLE 'TEST'.'EMPLOYEE'\
(\
'ID' INT NOT NULL DEFAULT 0 ,\
'FIRST_NAME' VARCHAR(100) NOT NULL ,
'LAST_NAME' VARCHAR(100) NULL ,\
'STAT_CD' TINYINT NOT NULL DEFAULT 0 \
);
""";

System.out.println(dbSchema);

输出结果:

1
2
|CREATE TABLE 'TEST'.'EMPLOYEE'('ID' INT NOT NULL DEFAULT 0 ,'FIRST_NAME' VARCHAR(100) NOT NULL ,
'LAST_NAME' VARCHAR(100) NULL ,'STAT_CD' TINYINT NOT NULL DEFAULT 0 );
  1. 右侧空格补充

如果出于某种原因不想去掉缩进,可以使用’\s’ (ASCII字符32,空格)转义序列。在任何一行的末尾使用它可以保证一行在遇到’\s’之前都有空格字符。

1
2
3
4
5
6
7
8
9
10
11
String dbSchema =   """
CREATE TABLE 'TEST'.'EMPLOYEE' \s
( \s
'ID' INT NOT NULL DEFAULT 0 , \s
'FIRST_NAME' VARCHAR(100) NOT NULL , \s
'LAST_NAME' VARCHAR(100) NULL , \s
'STAT_CD' TINYINT NOT NULL DEFAULT 0 \s
); \s
""";

System.out.println(dbSchema.replaceAll("\s", "."));

输出结果:

注:为便于理解,我们用’.’替换了所有空格。

1
2
3
4
5
6
7
CREATE.TABLE.'TEST'.'EMPLOYEE'...........
(........................................
..'ID'.INT.NOT.NULL.DEFAULT.0.,..........
...'FIRST_NAME'.VARCHAR(100).NOT.NULL.,..
..'LAST_NAME'.VARCHAR(100).NULL.,........
..'STAT_CD'.TINYINT.NOT.NULL.DEFAULT.0...
);.......................................

使用场景

  1. 只有在提高代码的清晰度时才使用文本块,特别是对于多行字符串。
  2. 如果字符串符合使用条件,请始终使用字符串。它们在应用程序性能方面更好。
  3. 为了保持所需的缩进,始终使用相对于内容的最后一行的三引号结束位置。
  4. 避免在复杂表达式(如lambda表达式或流操作)中出现内联文本块,以保持可读性。可考虑重构为局部变量或静态final字段。
  5. 文本块的缩进只使用空格或制表符。混合使用会导致文本对齐出现问题。

JEP 358 - 空指针问题定位

Java 14 通过精确地描述哪个变量为null,提高了由JVM生成的NullPointerException的可用性。

首先,我们需要传递-XX:+ShowCodeDetailsInExceptionMessages JVM参数,以便在运行应用程序时启用该特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HelpfulNullPointerException 
{
public static void main(String[] args)
{
Employee e = null;

System.out.println(e.getName());
}
}

class Employee {
Long id;
String name;

public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

输出错误日志:

1
2
3
4
Exception in thread "main" java.lang.NullPointerException: 
Cannot invoke "com.howtodoinjava.core.basic.Employee.getName()" because "e" is null
at com.howtodoinjava.core.basic.HelpfulNullPointerException.main
(HelpfulNullPointerException.java:9)

可以看到现在日志清晰的告诉我们哪个方法哪个变量为null而引发的异常。

没有此特性之前的输出日志:

1
2
3
Exception in thread "main" java.lang.NullPointerException
at com.howtodoinjava.core.basic.HelpfulNullPointerException.main
(HelpfulNullPointerException.java:9)

注意点:

  1. 只有由JVM直接创建和抛出的NPEs才会包含null的细节消息(当我们在程序中创建异常时,通常会在构造函数中传递这些消息)。运行在JVM上的程序显式地创建和(或)抛出的NPEs不受字节码分析的影响。
  2. 请注意,由于一些原因,可能在所有情况下都不需要null细节消息。例如,它会影响性能,因为算法会给堆栈跟踪的生成增加一些开销。
  3. 此外,它还增加了安全风险,因为null细节消息提供了对源代码的洞察,否则就不容易获得这些信息。

JEP 359 - record(预览特性)

Java 中的record类型。它是在 Java 14 中作为预览特性引入的,用于修饰普通的不可变数据类,实现类和应用程序之间的数据传输。

record 类型

enum一样,record也是 Java 中的一种特殊类类型。它的目的是用于创建类仅作为纯数据载体的地方。

classrecord之间的重要区别在于,record旨在消除设置和从实例获取数据所需的所有样板代码。record将这个责任转移给Java编译器,Java编译器帮我们生成构造函数、字段getterhashCode()equals()以及toString()等方法。

我们可以在record定义中覆盖上面提供的任何默认方法来实现自定义行为。

  1. 语法

使用关键字record在Java中创建这样的record类。就像我们在构造函数中所做的一样,我们需要在record中设置属性和它们的类型。

在给定的示例中,EmployeeRecord用于保存员工信息,如下。

1
2
3
4
5
6
7
8
9
package com.howtodoinjava.core.basic;

public record EmployeeRecord(Long id,
String firstName,
String lastName,
String email,
int age) {

}
  1. 创建和使用

要创建一个record,需要调用它的构造函数并将所有字段信息传递进去。然后,我们可以使用JVM生成的getter方法获取记录信息,并调用任何生成的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.howtodoinjava.core.basic;

public class RecordExample {
public static void main(String[] args)
{
EmployeeRecord e1 = new EmployeeRecord
(1l, "Lokesh", "Gupta", "howtodoinjava@gmail.com", 38);

System.out.println(e1.id());
System.out.println(e1.email());

System.out.println(e1);
}
}

输出结果:

1
2
3
4
1
howtodoinjava@gmail.com
EmployeeRecord[id=1, firstName=Lokesh, lastName=Gupta,
email=howtodoinjava@gmail.com, age=38]
  1. 底层原理

当我们创建EmployeeRecord记录时,编译器创建字节代码并在生成的类文件中包括以下内容:

  1. 一个包含所有字段的构造函数。
  2. toString()方法的作用是:打印record中所有字段的状态/值。
  3. equals()hashCode()方法使用基于 动态反射(invokedynamic)的机制。
  4. getter方法的名字类似于字段名,例如id()firstName()lastName()email()age()
  5. 这个类继承自java.lang.Record,它是所有record的基类。这意味着record不能继承其他类。
  6. 这个类被标记为final类型,这意味着我们不能创建它的子类。
  7. 它没有任何setter方法,这意味着record实例被设计为不可变的。

如果我们在生成的类文件上运行javap工具,我们将看到类文件的相关内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final class com.howtodoinjava.core.basic.EmployeeRecord 
extends java.lang.Record {
//1
public com.howtodoinjava.core.basic
.EmployeeRecord(java.lang.Long, java.lang.String, java.lang.String, java.lang.String, int);

//2
public java.lang.String toString();

//3
public final int hashCode();
public final boolean equals(java.lang.Object);

//4
public java.lang.Long id();
public java.lang.String firstName();
public java.lang.String lastName();
public java.lang.String email();
public int age();
}

使用 record 的场景

  1. 在建模诸如领域模型类(可能通过ORM持久化)或数据传输对象(DTO)之类的东西时,record是理想的候选对象。
  2. 这些record在临时存储数据时很有用。例如,可以在JSON反序列化期间。通常在反序列化期间,我们不期望程序改变从JSON读取的数据。我们只是读取数据并将其传递给数据处理器或验证器。
  3. 另外,record不能替换可变Java bean,因为record在设计上是不可变的。
  4. 当一个类打算保存数据一段时间并且希望避免编写大量样板代码时,请使用record
  5. 我们可以在各种其他情况下使用record,例如保存方法、流连接、复合键的多个返回值,以及在数据结构(如树节点)中使用记录。

原理深度分析

  1. 动态反射(invokedynamic)

如果我们看Java编译器生成的字节码来检查toString()(以及equals()hashCode())的方法实现,那么它们是使用基于invokedynamic的机制实现的。

invokedynamic是一个字节码指令,它通过动态方法调用来实现动态语言(针对JVM)的相关功能。

  1. 无法被继承实现子类化

尽管所有record都继承了java.lang.Record类,我们仍然不能显式创建java.lang.Record的子类,编译器不会通过。

1
2
3
4
5
final class Data extends Record {
private final int unit;
}

// Compiler error : The type Data may not subclass Record explicitly

这意味着获得record的唯一方法是显式地声明一个record,并让javac创建类文件。

  1. 使用注解

我们可以向记录的组件添加适用于它们的注释。例如,我们可以对id字段应用@Transient注解。

1
2
3
4
5
6
7
8
9
public record EmployeeRecord(
@Transient Long id,
String firstName,
String lastName,
String email,
int age)
{
//
}
  1. 序列化

record的Java序列化与常规类的序列化不同。record对象的序列化形式是从该对象的最终实例字段派生的值序列。record对象的流格式与流中的普通对象的流格式相同。

在反序列化中,如果指定流类描述符的本地类等价于一个record类,则首先读取并重新构建流字段,以作为record的组件值;其次,通过以组件值作为参数(或者如果流中缺少组件值,则为组件类型的默认值)调用record的规范构造函数来创建record对象。

除非是显式声明,否则record类的serialVersionUID0L。对于record,也无需匹配serialVersionUID值。

无法自定义用于序列化record对象的过程;在序列化和反序列化期间,record类定义的任何特定于类的writeObjectreadObjectreadObjectNoDatareadResolvewriteExternalreadExternal方法都会被忽略。但是,writeReplace方法可用于返回要序列化的替代对象。

在执行任何序列化或反序列化之前,我们必须确保record必须是可序列化或可外部化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.io.Serializable;

public record EmployeeRecord (
Long id,
String firstName,
String lastName,
String email,
int age) implements Serializable
{

}

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class RecordExample {
public static void main(String[] args)
{
EmployeeRecord e1 = new EmployeeRecord
(1l, "Lokesh", "Gupta", "howtodoinjava@gmail.com", 38);

writeToFile(e1, "employee1");
System.out.println(readFromFile("employee1"));
}

static void writeToFile(EmployeeRecord obj, String path) {
try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream(path))){
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
}

static EmployeeRecord readFromFile(String path) {
EmployeeRecord result = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path))){
result = (EmployeeRecord) ois.readObject();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return result;
}
}

输出结果:

1
2
EmployeeRecord[id=1, firstName=Lokesh, lastName=Gupta, 
email=howtodoinjava@gmail.com, age=38]
  1. 其他字段和方法

可以添加新的字段和方法,但不建议添加。

添加到record的新字段(未添加到组件列表中)必须是静态的。也可以添加一个方法来访问记录字段的内部状态。

添加的字段和方法不会在编译器隐式生成的字节代码中使用,因此它们不是equals()hashCode()toString()等任何方法实现的一部分。我们必须根据需要显式地使用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public record EmployeeRecord(
Long id,
String firstName,
String lastName,
String email,
int age) implements Serializable
{
//additional field
static boolean minor;

//additional method
public String fullName()
{
return firstName + " " + lastName;
}
}
  1. Compact Constructor

我们可以在Compact Constructor中添加用于数据验证的构造函数特定代码。它有助于构建在给定业务上下文中有效的记录。

Compact Constructor不会导致编译器生成单独的构造函数。相反,在Compact Constructor中指定的代码将作为额外代码出现在规范构造函数的开始处。

我们不需要指定构造函数参数给字段的赋值,就像在规范构造函数中通常发生的那样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public record EmployeeRecord(
Long id,
String firstName,
String lastName,
String email,
int age) implements Serializable
{
public EmployeeRecord
{
if(age < 18)
{
throw new IllegalArgumentException(
"You cannot hire a minor person as employee");
}
}
}

API变化

  1. Class class

Class类有两个方法—isRecord()getRecordComponents()getRecordComponents()方法返回一个RecordComponent对象数组。

RecordComponentjava.lang.reflect包中的一个新类。它包含十一个方法,用于检索注释和泛型类型的详细信息。

  1. ElementType 枚举

ElementTyperecord新增了一个常量,RECORD_COMPONENT

  1. javax.lang.model.element

ElementKind枚举为record新增了三个新的常量和instanceof特性的模式匹配,即BINDING_VARIABLERECORDRECORD_COMPONENT

结语

Java record是一个非常有用的特性,对Java类型系统是一个很好的补充,有助于几乎完全地减少为简单数据载体类编写的样板代码。

但是我们使用时应当注意,不要试图自定义它的行为,最好使用默认的构造。

JEP 361 - switch 表达式(标准)

Java 14 中switch语句允许程序在运行时根据给定表达式的值有多个可能的执行路径。

求值表达式称为选择器表达式,它的类型必须是charbyteshortintCharacterByteShortIntegerStringenum

在Java 14中,使用switch表达式,整个switch块“获得一个值”,然后可以在同一语句中将该值赋给一个变量。

  1. 例子
  • 在Java 14中,它是一个标准特性。在Java 13和Java 12中,它作为预览特性。
  • 它支持多个case标签,并使用yield来代替旧的return关键字返回值。
  • 它还支持通过标签规则返回值(类似于lambda的箭头操作符)。
  • 如果使用箭头函数(->)操作符,可以跳过yield关键字,如isWeekDayV1_1()所示。
  • 如果使用冒号(:)操作符,则需要使用yield关键字,如isWeekDayV1_2()所示。
  • 对于多个语句,使用大括号和yield关键字,如isWeekDayV2()所示。
  • 对于enum,我们可以跳过默认情况。如果有任何丢失的值没有在case中处理,编译器将会报错。在所有其他表达式类型(int, String等)中,我们也必须提供默认情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class SwitchExpressions 
{
public static void main(String[] argv)
{
System.out.println(isWeekDayV1_1(Day.MON)); //true
System.out.println(isWeekDayV1_2(Day.MON)); //true
System.out.println(isWeekDayV2(Day.MON)); //true
}

//1 - 直接返回结果

enum Day {
MON, TUE, WED, THUR, FRI, SAT, SUN
};

public static Boolean isWeekDayV1_1 (Day day)
{
Boolean result = switch(day) {
case MON, TUE, WED, THUR, FRI -> true;
case SAT, SUN -> false;
};
return result;
}

public static Boolean isWeekDayV1_2 (Day day)
{
Boolean result = switch(day) {
case MON, TUE, WED, THUR, FRI : yield true;
case SAT, SUN : yield false;
};
return result;
}

//2 - 多条语句(部分处理后返回结果)

public static Boolean isWeekDayV2 (Day day)
{
Boolean result = switch(day) {
case MON, TUE, WED, THUR, FRI ->
{
System.out.println("It is WeekDay");
yield true;
}
case SAT, SUN ->
{
System.out.println("It is Weekend");
yield false;
}
};
return result;
}
}
  1. yield 和 return

return语句将控制权返回给方法或构造函数的调用者。yield语句通过使封闭的switch表达式产生指定的值来传递控制。

1
2
3
SwitchExpression:
YieldStatement:
yield Expression;
  • SwitchExpression试图找到一个正确的YieldStatement,以便将控制转移到最内层的yield目标。
  • SwitchExpression正常终止,表达式的值成为SwitchExpression的值。
  • 如果表达式的求值由于某种原因突然结束,那么yield语句也会由于同样的原因突然结束。

更多新特性

JEP 343 – 打包工具(孵化)

在 JDK 8 中,一个名为javapackager的工具作为 JavaFX 工具包的一部分发布。然而,随着 JDK 11 的发布,JavaFX 从 Java 中分离出来后,流行的javapackager就不再可用了。

这个 JEP 基于javapackager工具创建了一个简单的打包工具,该工具支持本地打包格式,为最终用户提供自然的安装体验。这些格式包括 Windows 上的 msi和exe, macOS 上的 pkg和dmg,以及Linux上的 deb和rpm。

该工具可以从命令行直接调用,也可以通过工具提供程序API以编程方式调用。

1
$ jpackage --name myapp --input lib --main-jar main.jar

JEP 345 – NUMA-Aware Memory Allocation for G1

在Numa(Non-Uniform Memory Access 非均匀内存访问)内存体系结构中,每个处理器接收少量的本地内存,但是其他核心被授予访问它的权限。

并行垃圾收集器(由-XX:+UseParallelGC启用),多年来一直支持Numa,并提高了跨多个套接字运行单个 JVM 的配置的性能。

有了这个 JEP , G1垃圾收集器得到了增强,可以在 Linux OS 下更好地管理内存。

JEP 349 – JFR 事件流

该JEP为进程内和进程外程序公开了JDK运行情况记录数据,方便我们进行监控。

以前要使用这些数据,用户必须启动记录、停止记录、将内容转储到磁盘,然后解析记录文件。虽然对于程序分析比较友好,因为通常一次记录至少一分钟的时间,但是它不适用于实时监控。

该JEP对包 jdk.jfr.consumerjdk.jfr模块进行了扩展,提供了异步订阅事件的功能。用户可以直接从磁盘存储库读取记录数据或流,而无需转储记录文件。

JEP 352 – 非易失性映射的字节缓冲区

这个 JEP 添加了一个新的特定于JDK的文件映射模式,这样FileChannel API就可以用来创建引用 NVM(non-volatile memory 非易失性内存)的MappedByteBuffer实例。NVM也被称为持久内存,用于永久存储数据。

当前对MappedByteBufer API的更改意味着它支持允许直接内存更新所需的所有行为,并提供实现持久数据类型(例如块文件系统、日志记录日志、持久对象等)的更高级别Java客户端库所需的持久性保证。

JEP 363 – 删除并发标记清除(CMS)垃圾收集器

这个 JEP 的目的是删除掉在**Java 9 (JEP 291)**中被标记为deprecated的CMS垃圾收集器。由于 CMS GC的代码难以理解维护,且两年内未有相关感兴趣人员进行维护和更新。

所以现在,CMS GC已经从Java 14中删除了。需要注意的是,CMS GCJava 13之前都是可用的。

JEP 367 – 删除 Pack200 工具和相关 API

java.util.jar包中删除pack200unpack200工具。Pack200 API(Java SE 5.0中引入的JAR文件的压缩方案)在Java SE 11中他们已经被标记为deprecated,不建议使用,且未来版本明确会删除。

JEP 370 – 外部内存访问API(孵化)

有了这个 JEP , Java 提供了一个 API 来允许 Java 程序安全有效地访问 Java 堆之外的外部内存。

目标是相同的 API 应该能够操作各种类型的外部内存(例如,本机内存、持久内存、托管堆内存等)。

无论操作的内存类型如何,API 都不应该破坏 JVM 的安全性。另外,在源代码中内存回收操作应该是显式的。

参考文章

  1. Java 14 – New Features and Improvements



-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道