天天看點

Java 14 有哪些新特性?

雲栖号: https://yqh.aliyun.com 第一手的上雲資訊,不同行業精選的上雲企業案例庫,基于衆多成功案例萃取而成的最佳實踐,助力您上雲決策!

記錄為 Java 提供了一種正确實作資料類的能力,不再需要為實作資料類而編寫冗長的代碼。下面就來看看 Java 14 中的記錄有哪些新特性。

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 對象,包含 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==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 翻譯,轉載請注明來源出處。

原文釋出時間:2020-01-14

本文作者:Nathan Esquenazi

本文來自阿裡雲雲栖号合作夥伴“

CSDN

”,了解相關資訊可以關注“