實戰 Java 16 值類型 Record - 2. Record 的基本用法

在上一篇文章實戰 Java 16 值類型 Record - 1. Record 的默認方法使用以及基於預編譯生成相關字節碼的底層實現中,咱們詳細分析了 Record 自帶的屬性以及方法和底層字節碼與實現。這一篇咱們來詳細說明 Record 類的用法。java

聲明一個 Record

Record 能夠單獨做爲一個文件的頂級類,即:
User.java 文件:編程

public record User(long id, String name, int age) {}

也能夠做爲一個成員類,即:微信

public class RecordTest {
    public record User(long id, String name, int age) {}
}

也能夠做爲一個本地類,即:ide

public class RecordTest {
    public void test() {
        record Mail (long id, String content){}
        Mail mail = new Mail(10, "content");
    }
}

不能用 abstract 修飾 Record 類,會有編譯錯誤。
能夠用 final 修飾 Record 類,可是這實際上是沒有必要的,由於 Record 類自己就是 final 的post

成員 Record 類,還有本地 Record 類,自己就是 static 的,也能夠用 static 修飾,可是沒有必要。this

和普通類同樣,Record 類能夠被 public, protected, private 修飾,也能夠不帶這些修飾,這樣就是 package-private 的。code

和通常類不一樣的是,Record 類的直接父類不是 java.lang.Object 而是 java.lang.Record。可是,Record 類不能使用 extends,由於 Record 類不能繼承任何類。對象

Record 類的屬性

通常,在 Record 類聲明頭部指定這個 Record 類有哪些屬性繼承

public record User(long id, String name, int age) {}

同時,能夠在頭部的屬性列表中運用註解:字符串

@Target({ ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}

public record User(@A @B long id, String name, int age) {}

可是,須要注意一點,這裏經過反射獲取 id 的註解的時候,須要經過對應的方式進行獲取,不然獲取不到,即 ElementType.FIELD 經過 Field 獲取,ElementType.RECORD_COMPONENT 經過 RecordComponent 獲取:

Field[] fields = User.class.getDeclaredFields();
Annotation[] annotations = fields[0].getAnnotations(); // 獲取到註解 @B

RecordComponent[] recordComponents = User.class.getRecordComponents();
annotations = recordComponents[0].getAnnotations(); // 獲取到註解 @A

Record 類體

Record 類屬性必須在頭部聲明,在 Record 類體只能聲明靜態屬性

public record User(long id, String name, int age) {
    static long anotherId;
}

Record 類體能夠聲明成員方法和靜態方法,和通常類同樣。可是不能聲明 abstract 或者 native 方法

public record User(long id, String name, int age) {
    public void test(){}
    public static void test2(){}
}

Record 類體也不能包含實例初始化塊,例如:

public record User(@A @B long id, String name, int age) {
    {
        System.out.println(); //編譯異常
    }
}

Record 成員

Record 的全部成員屬性,都是 public final 非 static 的,對於每個屬性,都有一個對應的無參數返回類型爲屬性類型方法名稱爲屬性名稱的方法,即這個屬性的 accessor。前面說這個方法是 getter 方法其實不太準確,由於方法名稱中並無 get 或者 is 而是隻是純屬性名稱做爲方法名。

這個方法若是咱們本身指定了,就不會自動生成:

public record User(long id) {
    @Override
    public long id() {
        return id;
    }
}

若是沒有本身指定,則會自動生成這樣一個方法:

  1. 方法名就是屬性名稱
  2. 返回類型就是對應的屬性類型
  3. 是一個 public 方法,而且沒有聲明拋出任何異常
  4. 方法體就是返回對應屬性
  5. 若是屬性上面有任何註解,那麼這個註解若是能加到方法上那麼也會自動加到這個方法上。例如:
    public record User(@A @B long id, String name, int age) {}
    @Target({
        ElementType.RECORD_COMPONENT,
        ElementType.METHOD,
    })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface A {}
    @Target({ ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface B {}

    下面獲取 id() 這個方法的註解則會獲取到註解 @A

    Method id = User.class.getDeclaredMethod("id");
    Annotation[] idAnnotations = id.getAnnotations(); //@A

因爲會自動生成這些方法,因此 Record 成員的名稱不能和 Object 的某些不符合上述條件(即上面提到的 6 條)的方法的名稱同樣,例如:

public record User(
    int wait, //編譯錯誤
    int hashcode, //這個不會有錯誤,由於 hashcode() 方法符合自動生成的 accessor 的限制條件。
    int toString, //編譯錯誤
    int finalize //編譯錯誤) {
}

Record 類若是沒有指定,則默認會生成實現 java.lang.Record 的抽象方法,即hashcode(), equals(), toStrng() 這三個方法。這三個方法的實現方式,在第一節已經詳細分析過,這裏簡單回顧下要點:

  1. hashcode() 在編譯的時候自動生成字節碼實現,核心邏輯基於 ObjectMethodsmakeHashCode 方法,裏面的邏輯是對於每個屬性的哈希值移位組合起來。注意這裏的全部調用(包括對於 ObjectMethods 的方法調用以及獲取每一個屬性)都是利用 MethodHandle 實現的近似於直接調用的方式調用的。
  2. equals() 在編譯的時候自動生成字節碼實現,核心邏輯基於 ObjectMethodsmakeEquals 方法,裏面的邏輯是對於兩個 Record 對象每個屬性判斷是否相等(對於引用類型用Objects.equals(),原始類型使用 ==),注意這裏的全部調用(包括對於 ObjectMethods 的方法調用以及獲取每一個屬性)都是利用 MethodHandle 實現的近似於直接調用的方式調用的。
  3. toString() 在編譯的時候自動生成字節碼實現,核心邏輯基於 ObjectMethodsmakeToString 方法,裏面的邏輯是對於每個屬性的值組合起來構成字符串。注意這裏的全部調用(包括對於 ObjectMethods 的方法調用以及獲取每一個屬性)都是利用 MethodHandle 實現的近似於直接調用的方式調用的。

Record 構造器

若是沒有指定構造器,Record 類會自動生成一個以全部屬性爲參數而且給每一個屬性賦值的構造器。咱們能夠經過兩種方式本身聲明構造器:

第一種是明確聲明以全部屬性爲參數而且給每一個屬性賦值的構造器,這個構造器須要知足:

  1. 構造器參數須要包括全部屬性(同名同類型),並按照 Record 類頭的聲明順序。
  2. 不能聲明拋出任何異常(不能使用 throws)
  3. 不能調用其餘構造器(即不能使用 this(xxx))
  4. 構造器須要對於每一個屬性進行賦值。

對於其餘構造器,須要明確調用這個包含全部屬性的構造器

public record User(long id, String name, int age) {
    public User(int age, long id) {
        this(id, "name", age);
    }
    public User(long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}

第二種是簡略方式聲明,例如:

public record User(long id, String name, int age) {
    public User {
        System.out.println("initialized");
        id = 1000 + id;
        name = "prefix_" + name;
        age = 1 + age;
        //在這以後,對每一個屬性賦值
    }
}

這種方式至關於省略了參數以及對於每一個屬性賦值,至關於對這種構造器的開頭插入代碼。

微信搜索「個人編程喵」關注公衆號,每日一刷,輕鬆提高技術,斬獲各類offer

image

相關文章
相關標籤/搜索