转载

Java 14 有哪些新特性?

Java 14 有哪些新特性?

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

Java 14 有哪些新特性?

作者 |  Nathan Esquenazi

译者 |  弯月 ,责编 | 郭芮

出品 | CSDN(ID:CSDNnews)

以下为译文:

Java 14 即将在 2020 年 3 月正式发布。 Java 以 6 个月作为新版本的发布周期,和之前的版本发布一样,JDK 14 预计将在语言本身和 JVM 级别上带来一些新特性。

如果我们看一下特性列表,我们会注意到一些开发者非常期待的语言特性:记录 (records)、 switch 表达式(在 JDK 13 中就已经存在,不过仅仅是预览模式),模式匹配。 下面让我们看下其中比较有趣的记录这一特性。

Java 14 有哪些新特性?

前提条件

我们需要 OpenJDK 网站 中的 JDK 14 先期预览版本( https://jdk.java.net/14/ )。

Java 14 有哪些新特性?

什么是一条记录?

记录表示“数据类” ,是用于保存纯数据的一种特殊的类。 其他语言中已经有类似记录的结构,比如 Kotlin 的数据类。 通过将类型声明为记录,通过类型即可表达意图,即只表示数据。 声明记录的语法比使用普通类要简单得多,普通类通常需要实现核心 Object 方法,如 equals ()和 hashCode () (通常称为“样板”代码)。 在对于模型类 (可能通过 ORM 持久化) 或数据传输对象 (DTOs) 等事物建模时,记录是一个不错的选择。

如果想知道记录如何在 Java 语言中实现的,可以参照枚举类型。 枚举也是一个具有特殊语义和优雅语法的类。 由于记录和枚举仍然是类,所以类中可用的许多特性都得到了保留,因此记录在设计的简单性和灵活性之间取得了平衡。

记录是一个预览语言特性,这意味着,尽管已经完全支持了这种特性,但是还没正式进入标准 JDK 中,目前只能通过激活标志来使用。 预览语言功能可能在未来的版本中更新或删除。 switch 达式也与之相似,它可能在未来的版本中永存。

Java 14 有哪些新特性?

一个记录的例子

下面给出一个记录的范例:

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.

Java 14 有哪些新特性?

揭露其神秘面纱

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

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

javap -v -p Person.class

字节码:

Classfile examples/Person.class

Last modified Dec  222019 ; size  1273 bytes

SHA- 256 checksum  6f 1b325121ca32a0b6127180eff29dcac4834f9c138c9613c526a4202fef972f

Compiled from  "Person.java"

final class   examples . Person   extends   java . lang . Record

minor version : 65535

major version : 58

flags : (0 x0030ACC_FINALACC_SUPER

this_class : #8                          //  examples / Person

super_class : #2                         //  java / lang / Record

interfaces : 0,  fields : 2,  methods : 6,  attributes : 4

Constant pool :

= 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;

14return

LineNumberTable:

line  30

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  30

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  30

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  30

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  30

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  30

}

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. 两个组件: firstName lastName 都是用 private final 的。
  4. 有一个提供构造对象的公有构造函数: public examples.Person(java.lang.String, java.lang.String) 通过查看它的字节码,我们可以知道,这个构造函数只是将两个参数赋值给这两个组件。 该构造函数等价于:

    public   Person (String firstName, String lastName)   {

    this .firstName = firstName;

    this .lastName = lastName;

    }

  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

Java 14 有哪些新特性?

在记录的声明中添加成员

我们不能向记录中添加实例字段,这在意料之中,因为这种数据应该设置为组件。 但是我们可以添加静态字段:

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 14 有哪些新特性?

结论

记录为 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 技术演进

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

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

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

原文  http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650737168&idx=3&sn=3e1c297597e8a80e9a08960ab637721b
正文到此结束
Loading...