记录为 Java 提供了一种正确实现数据类的能力,不再需要为实现数据类而编写冗长的代码。下面就来看看 Java 14 中的记录有哪些新特性。

作者 | Nathan Esquenazi

译者 | 明明如月,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下为译文:

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 对象,包含 firstNamelastName 两个组件,记录的 body 为空。

然后我们对其进行编译。注意 --enable-preview 选项。

 javac --enable-preview --release 14 Person.javaNote: Person.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

揭露其神秘面纱

正如前面提到的,记录只是一个用于保存和暴露数据的类。

接下来让我们来看看用 javap 工具生成的字节码:

javap -v -p Person.class

字节码:

Classfile examples/Person.classLast modified Dec 22, 2019; size 1273 bytesSHA-256 checksum 6f1b325121ca32a0b6127180eff29dcac4834f9c138c9613c526a4202fef972fCompiled from "Person.java"
final class examples.Person extends java.lang.Recordminor version: 65535major version: 58flags: (0x0030) ACC_FINAL, ACC_SUPERthis_class: #8                          // examples/Personsuper_class: #2                         // java/lang/Recordinterfaces: 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_FINALprivate final java.lang.String lastName;descriptor: Ljava/lang/String;flags: (0x0012) ACC_PRIVATE, ACC_FINALpublic examples.Person(java.lang.String, java.lang.String);descriptor: (Ljava/lang/String;Ljava/lang/String;)Vflags: (0x0001) ACC_PUBLICCode:stack=2, locals=3, args_size=30: aload_01: invokespecial #1                  // Method java/lang/Record."":()V4: aload_05: aload_16: putfield      #7                  // Field firstName:Ljava/lang/String;9: aload_010: aload_211: putfield      #13                 // Field lastName:Ljava/lang/String;14: returnLineNumberTable:line 3: 0MethodParameters:Name                           FlagsfirstNamelastNamepublic java.lang.String toString();descriptor: ()Ljava/lang/String;flags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokedynamic #18,  0             // InvokeDynamic #0:toString:(Lexamples/Person;)Ljava/lang/String;6: areturnLineNumberTable:line 3: 0public final int hashCode();descriptor: ()Iflags: (0x0011) ACC_PUBLIC, ACC_FINALCode:stack=1, locals=1, args_size=10: aload_01: invokedynamic #22,  0             // InvokeDynamic #0:hashCode:(Lexamples/Person;)I6: ireturnLineNumberTable:line 3: 0public final boolean equals(java.lang.Object);descriptor: (Ljava/lang/Object;)Zflags: (0x0011) ACC_PUBLIC, ACC_FINALCode:stack=2, locals=2, args_size=20: aload_01: aload_12: invokedynamic #26,  0             // InvokeDynamic #0:equals:(Lexamples/Person;Ljava/lang/Object;)Z7: ireturnLineNumberTable:line 3: 0public java.lang.String firstName();descriptor: ()Ljava/lang/String;flags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #16                 // Field firstName:Ljava/lang/String;4: areturnLineNumberTable:line 3: 0public java.lang.String lastName();descriptor: ()Ljava/lang/String;flags: (0x0001) ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #17                 // Field lastName:Ljava/lang/String;4: areturnLineNumberTable: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

我们要特别重视以下几点:

  1. 这个类被标记为 final ,意味着不能创建子类。

  2. 和所有的枚举都以 java.lang.Enum 为基类一样, 所有的记录都以 java.lang.Record 为基类。

  3. 两个组件: firstNamelastName 都是用 privatefinal 的。

  4. 有一个提供构造对象的公有构造函数:public examples.Person(java.lang.String, java.lang.String) 。通过查看它的字节码,我们可以知道,这个构造函数只是将两个参数赋值给这两个组件。该构造函数等价于:

  5. 有两个获取对象值的方法,分别为 firstName()lastName().

  6. 自动生成 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==null||lastName==null){throw new IllegalArgumentException("firstName and lastName must not be null");// 你可以忽略属性赋值,编译器会自动为你添加赋值代码}public Person(String fullName){this(fullName.split("")[0], fullName.split("")[1]);}
}

结论

记录为 Java 提供了一种正确实现数据类的能力,不再需要为实现数据类而编写冗长的代码。 这让编写纯数据类代码从几行缩减为一行代码。 还有一些其他预览的语言特性可以和记录搭配使用,比如模式匹配。 如果想深入了解记录和相关背景,请参阅 Brian Goetz 的 OpenJDK 文档(https://cr.openjdk.java.net/~briangoetz/amber/datum.html)。

原文:https://dzone.com/articles/a-first-look-at-records-in-java-14

作者: Mahmoud Anouti,高级软件工程师。译者:明明如月,知名互联网公司 Java 高级开发工程师,CSDN 博客专家。

本文为 CSDN 翻译,转载请注明来源出处。

热 文 推 荐 

☞京东回应「被薅 7000 万、项目组全体开除」;微信朋友圈屏蔽支付宝集五福;MySQL 8.0.19 发布 | 极客头条

☞不再设立 Flag,马克·扎克伯格的新年寄语!

☞PHP 可能在未来十年内消失?

☞铁打的春晚,流水的互联网公司

☞达摩院 2020 预测:模块化降低芯片设计门槛 | 问底中国 IT 技术演进

☞千万不要和程序员一起合租!

☞在调查过基于模型的强化学习方法后,我们得到这些结论

☞漫话:如何给女朋友解释为什么一到年底,部分网站就会出现日期混乱的现象?

你点的每个“在看”,我都认真当成了喜欢

Java 14 有哪些新特性?相关推荐

  1. Java 7~14各个版本新特性详解

    Java 7 特性列表 switch中添加对String类型的支持 数字字面量的改进 / 数值可加下划 异常处理(捕获多个异常) try-with-resources 增强泛型推断 JSR203 NI ...

  2. java的发展(8-17新特性整理)

    java java的诞生与历史: 简介:Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. ...

  3. 不停歇的Java即将发布JDK16新特性速览及从菜鸟到架构师[图]

    一.不停歇的Java即将发布JDK16新特性速览 当开发者深陷Java8版本之际,这边下一版本Java16有了最新的消息,与Java15一样,作为短期版本,Oracle仅提供6个月的支持. 根据发布计 ...

  4. 详解 Java 17中的新特性:“密封类”

    作者:程序猿DD 博客:https://blog.didispace.com/ Java 17推出的新特性Sealed Classes经历了2个Preview版本(JDK 15中的JEP 360.JD ...

  5. Java 11 正式发布,新特性解读

    Java 11 正式发布,新特性解读 杨晓峰   2018 年 9 月 26 日 话题:Java语言 & 开发 不知不觉 JDK 11 已经发布了,从 9 开始,JDK 进入了让人学不动的更新 ...

  6. Java基础之Java8 新特性

    hp实训8.19_Java基础之Java8新特性 // 信息展示方法 ,接口中,就可以提供一种实现. 就可以使用这种功能.default void print() {System.out.printl ...

  7. 【JAVA拾遗】Java8新特性合辑

    [JAVA拾遗]Java8新特性合辑 文章目录 [JAVA拾遗]Java8新特性合辑 0. 逼逼 [--/--]126 Lambda Expressions & Virtual Extensi ...

  8. Java 17 版本的新特性

    Java 17 版本的新特性

  9. 详解Java 8十大新特性

    前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java ...

最新文章

  1. 7.1.3 TimePicker结合案例详解
  2. mysql条件填充命令_mysql的简单命令
  3. 使用STM32CubeMX,生成STM32F103ZE SPI3 HAL 工程
  4. Java并发:volatile内存可见性和指令重排
  5. Intel 酷睿i5 6300HQ与Intel 酷睿i7 6700HQ哪个好
  6. J2ME Nokia 模拟器 安装运行
  7. 使用qrcode类制作二维码
  8. 如何在几分钟内安装Red Hat Container Development Kit(CDK)
  9. MySQL中函数CONCAT及GROUP_CONCAT 对应oracle中的wm_concat
  10. replaceselection();java'_Java JTextComponent.replaceSelection方法代码示例
  11. JavaScript实现中国地图圆点标注(二十四)
  12. linux 附加数据库文件,SQL Server 数据库分离与附加图文详解
  13. sql返回刚添加的数据的自增id
  14. 人工智能:卷积神经网络及YOLO算法 入门详解与综述(二)
  15. ab压力测试post请求入参json格式处理
  16. python选股软件编写
  17. Windows下最快的磁盘空间分析软件——WizTree
  18. html制作带有尖角的边框,纯CSS3制作带尖角的气泡对话框实例特效代码
  19. 深度学习的GPU型号和参数选择
  20. Unity3D中如何制作身临其境的3d音效

热门文章

  1. 在Apache中利用ServerAlias设置虚拟主机接收多个域名和设置域名泛解析
  2. Windows Phone开发(40):漫谈关键帧动画之中篇 转:http://blog.csdn.net/tcjiaan/article/details/7559978...
  3. 两种数据仓库分层实例
  4. [python] 当前时间输出字符串
  5. configure 查看默认安装路径
  6. 计算机专业挂职锻炼,计算机学院挂职体验谈
  7. 原生开发什么意思_什么是原生开发?什么是混合开发?两者有什么区别?
  8. Flutter布局锦囊---屏幕顶部提醒
  9. 中国1-(4-羟基苯基)乙酮市场趋势报告、技术动态创新及市场预测
  10. android在搭建框架时要注意,Android开发搭建应用框架步骤和注意的问题