Java 14 有哪些新特性?
记录为 Java 提供了一种正确实现数据类的能力,不再需要为实现数据类而编写冗长的代码。下面就来看看 Java 14 中的记录有哪些新特性。
作者 | Nathan Esquenazi
译者 | 弯月,责编 | 郭芮
以下为译文:
Java 14 即将在 2020 年 3 月正式发布。 Java 以 6 个月作为新版本的发布周期,和之前的版本发布一样,JDK 14 预计将在语言本身和 JVM 级别上带来一些新特性。
如果我们看一下特性列表,我们会注意到一些开发者非常期待的语言特性:记录 (records)、 switch 表达式(在 JDK 13 中就已经存在,不过仅仅是预览模式),模式匹配。下面让我们看下其中比较有趣的记录这一特性。
前提条件
我们需要 OpenJDK 网站中的 JDK 14 先期预览版本(https://jdk.java.net/14/)。
什么是一条记录?
记录表示“数据类” ,是用于保存纯数据的一种特殊的类。 其他语言中已经有类似记录的结构,比如 Kotlin 的数据类。 通过将类型声明为记录,通过类型即可表达意图,即只表示数据。 声明记录的语法比使用普通类要简单得多,普通类通常需要实现核心 Object 方法,如 equals 和 hashCode (通常称为“样板”代码)。 在对于模型类 (可能通过 ORM 持久化) 或数据传输对象 (DTOs) 等事物建模时,记录是一个不错的选择。
如果想知道记录如何在 Java 语言中实现的,可以参照枚举类型。 枚举也是一个具有特殊语义和优雅语法的类。 由于记录和枚举仍然是类,所以类中可用的许多特性都得到了保留,因此记录在设计的简单性和灵活性之间取得了平衡。
记录是一个预览语言特性,这意味着,尽管已经完全支持了这种特性,但是还没正式进入标准 JDK 中,目前只能通过激活标志来使用。 预览语言功能可能在未来的版本中更新或删除。 switch 达式也与之相似,它可能在未来的版本中永存。
一个记录的例子
下面给出一个记录的范例:
package examples;
record Person (String firstName, String lastName) {}
我们定义了一个 Person
对象,包含firstName
和lastName
两个组件,记录的 body 为空。
然后我们对其进行编译。注意 --enable-preview
选项。
javac --enable-preview --release 14 Person.java
Note: Person.java uses preview language features.
Note: Recompile with -Xlint:preview for details.
揭露其神秘面纱
正如前面提到的,记录只是一个用于保存和暴露数据的类。
接下来让我们来看看用 javap 工具生成的字节码:
javap -v -p Person.class
字节码:
Classfile examples/Person.class
Last modified Dec 22, 2019; size 1273 bytes
SHA-256 checksum 6f1b325121ca32a0b6127180eff29dcac4834f9c138c9613c526a4202fef972f
Compiled from "Person.java"
final class examples.Person extends java.lang.Record
minor version: 65535
major version: 58
flags: (0x0030) ACC_FINAL, ACC_SUPER
this_class: #8 // examples/Person
super_class: #2 // java/lang/Record
interfaces: 0, fields: 2, methods: 6, attributes: 4
Constant pool:
#1 = Methodref #2.#3 // java/lang/Record."":V
#2 = Class #4 // java/lang/Record
#3 = NameAndType #5:#6 // "":V
#4 = Utf8 java/lang/Record
#5 = Utf8
#6 = Utf8 V
#7 = Fieldref #8.#9 // examples/Person.firstName:Ljava/lang/String;
#8 = Class #10 // examples/Person
#9 = NameAndType #11:#12 // firstName:Ljava/lang/String;
#10 = Utf8 examples/Person
#11 = Utf8 firstName
#12 = Utf8 Ljava/lang/String;
#13 = Fieldref #8.#14 // examples/Person.lastName:Ljava/lang/String;
#14 = NameAndType #15:#12 // lastName:Ljava/lang/String;
#15 = Utf8 lastName
#16 = Fieldref #8.#9 // examples/Person.firstName:Ljava/lang/String;
#17 = Fieldref #8.#14 // examples/Person.lastName:Ljava/lang/String;
#18 = InvokeDynamic #0:#19 // #0:toString:(Lexamples/Person;)Ljava/lang/String;
#19 = NameAndType #20:#21 // toString:(Lexamples/Person;)Ljava/lang/String;
#20 = Utf8 toString
#21 = Utf8 (Lexamples/Person;)Ljava/lang/String;
#22 = InvokeDynamic #0:#23 // #0:hashCode:(Lexamples/Person;)I
#23 = NameAndType #24:#25 // hashCode:(Lexamples/Person;)I
#24 = Utf8 hashCode
#25 = Utf8 (Lexamples/Person;)I
#26 = InvokeDynamic #0:#27 // #0:equals:(Lexamples/Person;Ljava/lang/Object;)Z
#27 = NameAndType #28:#29 // equals:(Lexamples/Person;Ljava/lang/Object;)Z
#28 = Utf8 equals
#29 = Utf8 (Lexamples/Person;Ljava/lang/Object;)Z
#30 = Utf8 (Ljava/lang/String;Ljava/lang/String;)V
#31 = Utf8 Code
#32 = Utf8 LineNumberTable
#33 = Utf8 MethodParameters
#34 = Utf8 Ljava/lang/String;
#35 = Utf8 I
#36 = Utf8 (Ljava/lang/Object;)Z
#37 = Utf8 SourceFile
#38 = Utf8 Person.java
#39 = Utf8 Record
#40 = Utf8 BootstrapMethods
#41 = MethodHandle 6:#42 // REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#42 = Methodref #43.#44 // java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#43 = Class #45 // java/lang/runtime/ObjectMethods
#44 = NameAndType #46:#47 // bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#45 = Utf8 java/lang/runtime/ObjectMethods
#46 = Utf8 bootstrap
#47 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
#48 = String #49 // firstName;lastName
#49 = Utf8 firstName;lastName
#50 = MethodHandle 1:#7 // REF_getField examples/Person.firstName:Ljava/lang/String;
#51 = MethodHandle 1:#13 // REF_getField examples/Person.lastName:Ljava/lang/String;
#52 = Utf8 InnerClasses
#53 = Class #54 // java/lang/invoke/MethodHandles$Lookup
#54 = Utf8 java/lang/invoke/MethodHandles$Lookup
#55 = Class #56 // java/lang/invoke/MethodHandles
#57 = Utf8 Lookup
{
private final java.lang.String firstName;
descriptor: Ljava/lang/String;
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
private final java.lang.String lastName;
descriptor: Ljava/lang/String;
flags: (0x0012) ACC_PRIVATE, ACC_FINAL
public examples.Person(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Record."":V
4: aload_0
5: aload_1
6: putfield #7 // Field firstName:Ljava/lang/String;
9: aload_0
10: aload_2
11: putfield #13 // Field lastName:Ljava/lang/String;
14: return
LineNumberTable:
line 3: 0
MethodParameters:
Name Flags
firstName
lastName
public java.lang.String toString;
descriptor: Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(Lexamples/Person;)Ljava/lang/String;
6: areturn
LineNumberTable:
line 3: 0
public final int hashCode;
descriptor: I
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #22, 0 // InvokeDynamic #0:hashCode:(Lexamples/Person;)I
6: ireturn
LineNumberTable:
line 3: 0
public final boolean equals(java.lang.Object);
descriptor: (Ljava/lang/Object;)Z
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokedynamic #26, 0 // InvokeDynamic #0:equals:(Lexamples/Person;Ljava/lang/Object;)Z
7: ireturn
LineNumberTable:
line 3: 0
public java.lang.String firstName;
descriptor: Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #16 // Field firstName:Ljava/lang/String;
4: areturn
LineNumberTable:
line 3: 0
public java.lang.String lastName;
descriptor: Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #17 // Field lastName:Ljava/lang/String;
4: areturn
LineNumberTable:
line 3: 0
}
SourceFile: "Person.java"
Record:
java.lang.String firstName;
descriptor: Ljava/lang/String;
java.lang.String lastName;
descriptor: Ljava/lang/String;
BootstrapMethods:
0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
Method arguments:
#8 examples/Person
#48 firstName;lastName
#50 REF_getField examples/Person.firstName:Ljava/lang/String;
#51 REF_getField examples/Person.lastName:Ljava/lang/String;
InnerClasses:
public static final #57= #53 of #55; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
我们要特别重视以下几点:
这个类被标记为 final ,意味着不能创建子类。
和所有的枚举都以
java.lang.Enum
为基类一样, 所有的记录都以java.lang.Record
为基类。两个组件:
firstName
和lastName
都是用private
和final
的。有一个提供构造对象的公有构造函数:
public examples.Person(java.lang.String, java.lang.String)
。通过查看它的字节码,我们可以知道,这个构造函数只是将两个参数赋值给这两个组件。该构造函数等价于:public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
有两个获取对象值的方法,分别为
firstName
和lastName
.自动生成
toString
,hashCode
和equals
三个函数。他们都依赖invokedynamic
来实现动态调用包含隐式实现函数在内的方法。从字节码中可以看到,有一个启动函数ObjectMethods.bootstrap
来根据记录组件的名称和它的 Getter 函数,生成对应的函数。他们的表现和我们设想的一致:
Person john = new Person("John", "Doe");
System.out.println(john.firstName); // John
System.out.println(john.lastName); // Doe
System.out.println(john); // Person[firstName=John, lastName=Doe]
Person jane = new Person("Jane", "Dae");
Person johnCopy = new Person("John", "Doe");
System.out.println(john.hashCode); // 71819599
System.out.println(jane.hashCode); // 71407578
System.out.println(johnCopy.hashCode); // 71819599
System.out.println(john.equals(jane)); // false
System.out.println(john.equals(johnCopy)); // true
在记录的声明中添加成员
我们不能向记录中添加实例字段,这在意料之中,因为这种数据应该设置为组件。但是我们可以添加静态字段:
record Person(String firstName, String lastName){
static int x;
}
我们可以定义静态函数和实例函数来操作对象的状态。
record Person (String firstName, String lastName) {
static int x;
public static void dox{
x++;
}
public String getFullName{
return firstName + " " + lastName ;
}
}
我们也可以为记录添加构造函数,也可以编辑规范构造函数(带有两个字符串参数的构造函数)。如果你想重写规范构造函数,你可以编写一个不带参数的构造函数,不需要对属性进行赋值。
record Person (String firstName, String lastName) {
public Person {
if(firstName==||lastName==){
throw new IllegalArgumentException("firstName and lastName must not be ");
// 你可以忽略属性赋值,编译器会自动为你添加赋值代码
}
public Person(String fullName){
this(fullName.split("")[0], fullName.split("")[1]);
}
}
结论
记录为 Java 提供了一种正确实现数据类的能力,不再需要为实现数据类而编写冗长的代码。 这让编写纯数据类代码从几行缩减为一行代码。 还有一些其他预览的语言特性可以和记录搭配使用,比如模式匹配。 如果想深入了解记录和相关背景,请参阅 Brian Goetz 的 OpenJDK 文档(https://cr.openjdk.java.net/~briangoetz/amber/datum.html)。
推荐:开化县属于哪个市?
Java 14 有哪些新特性?的更多相关文章
- 使用示例带你提前了解 Java 9 中的新特性
使用示例带你提前了解 Java 9 中的新特性 转载来源:https://juejin.im/post/58c5e402128fe100603cc194 英文出处:https://www.journa ...
- 一文带你看遍 JDK9~14 的重要新特性!
Java9 发布于 2017 年 9 月 21 日 .作为 Java8 之后 3 年半才发布的新版本,Java 9 带 来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如 ...
- JAVA JDK1.5-1.9新特性
1.51.自动装箱与拆箱:2.枚举(常用来设计单例模式)3.静态导入4.可变参数5.内省 1.61.Web服务元数据2.脚本语言支持3.JTable的排序和过滤4.更简单,更强大的JAX-WS5.轻量 ...
- Java 8 正式发布,新特性全搜罗
经过2年半的努力.屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本. Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码: ...
- Java引入的一些新特性
Java引入的一些新特性 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程, ...
- Java学习之==>Java8 新特性详解
一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...
- Java各个版本的新特性
原链接:http://blog.csdn.net/shareus/article/details/50736159 1.5 1.自动装箱与拆箱: 2.枚举(常用来设计单例模式) http://www. ...
- C++11 & C++14 & C++17新特性
C++11:C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto.decltype,和模板的大量改进. 新的关键字 auto C++11中引入auto第一种作用是为了自动类型推导 ...
- Java Servlet 3.0 新特性
Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布.该版本在前一版本(Servlet 2.5)的基础上提供了若 ...
随机推荐
- 面试题(9)之 leetcode-189
题目描述 解法一: /** * @param {number[]} nums * @param {number} k * @return {void} Do not return anything, ...
- JNI操作二维数组
之前的文章讲解了有关JNI使用方法,这篇文章进阶一点,介绍下JNI操作二维数组的方法.有了之前文章的操作JNI的方法,这里直接上代码了. Java代码部分 package com.testjni; p ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-barcode
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...
- sed -i添加到第一行
用sed的i\命令在第一行前面插入即可,加上 -i 选项直接操作文件. sed -i '1i\要添加的内容' yourfile 查看插入第一行是否成功 sed -n '1,1p' yourfile
- 面试官,不要再问我“Java虚拟机类加载机制”了(转载)
关于Java虚拟机类加载机制往往有两方面的 面试题:根据程序判断输出结果和讲讲虚拟机类加载机制的流程.其实这两类题本质上都是考察面试者对Java虚拟机类加载机制的了解. 面试题试水 现在有这样一道判断 ...
- storm on yarn(CDH5) 部署笔记
按照storm on yarn(Apache hadoop)部署好之后,然后修改HADOOP_HOME,hadoopenv.sh中的JAVA_HOME,以及storm-yarn-master中pom. ...
- 深入X64架构(翻译)
| 本人只是原创翻译,而且翻译也不一定好,纯当锻炼.内容如果英文好的同学,建议直接去看英文原版,比较爽. NBAOL系列2代产品是 windows平台64位的应用程序,在技术测试过程中,遇到一些cra ...
- 原子类型字段更新器AtomicXxxxFieldUpdater
本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 原子类型字段更新器 在java.util.concurr ...
- HZNU-ACM寒假集训Day10小结 树-树形DP
树形DP 加分二叉树 洛谷P1040 注意中序遍历的特点:当根节点编号k时,编号小于k的都在其左子树上,编号大于k的都在右子树 转移方程 f[i,j]=max{f[i,k-1]*f[k+1,j]+d[ ...
- 实验吧web-易-FALSE
先审计代码: <?php if (isset($_GET['name']) and isset($_GET['password'])) { if ($_GET['name'] == $_GET[ ...