CodeGym /課程 /JAVA 25 SELF /record 與 class 的差異、record 的限制

record 與 class 的差異、record 的限制

JAVA 25 SELF
等級 22 , 課堂 4
開放

1. record 與 class 的比較:主要差異是什麼?

在 Java 中,我們有兩種主要方式來描述自訂資料型別:一般類別 (class) 與 record 類別 (record)。乍看之下,兩者都能用來儲存並處理資料;但深入一點就會發現,差異比想像中多!

差異表: class vs record

特性 一般類別 (class) record 類別 (record)
可變性 任意:欄位可標示為 final,也可以不是 不變:所有欄位皆為 final
繼承 可繼承(extends),預設不是 final 一律為 final,不能作為超類別
欄位 不拘:可為靜態或非靜態、final 或非 final、任意型別 僅有 record 元件(private final),外加靜態欄位
Getter/Setter 需自行撰寫(或用 Lombok 產生) 自動建立 getter(方法名稱即欄位名稱),沒有 setter
equals/hashCode/toString 通常手動撰寫/工具產生(equalshashCodetoString 依所有元件自動產生
建構子 可任意定義,多個也行 僅一個主要建構子(涵蓋所有元件),可新增精簡建構子
介面 可以實作 可以實作
其他方法 不拘 可新增,但只能是方法(不能新增欄位)
在集合中的使用 可以,但必須正確實作 equals/hashCode 非常適合作為鍵/值,相關實作已就緒

範例說明

一般類別:


public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

record 類別:


public record Person(String name, int age) { }

就這樣!一行程式碼——就能得到相同(甚至更好)的效果,而且不會遺漏重要實作。

2. record 類別的限制

record 不是「簡短語法」而已,而是一套有嚴格規範的概念。以下詳細說明。

record 一律為 final

依定義,record 類別一律為 final。也就是說,您無法從 record 建立子類別:


public record Point(int x, int y) { }

// public class ColoredPoint extends Point { } // 編譯錯誤!

若需要擴充行為,請使用一般類別或合成(把 record 放進類別中)。

record 不能作為超類別

record 類別不能作為其他類別的父類別,它一律是 final。這是合理的:如果可以,某人可能加上可變欄位——整個「不可變資料」的概念就會崩潰。

僅有 final 欄位(元件)

所有 record 元件都在標頭中宣告,且預設為 private final。您不能在 record 的主體中新增非靜態欄位:


public record User(String login, String email) {
    // int counter; // 錯誤!不可新增非靜態欄位
    static int totalUsers = 0; // 可以,這是靜態欄位
}

沒有 setter

record 類別不能為元件提供 setter。任何加入類似 setX(int x) 的方法都沒有意義:您無法在建立物件後變更欄位值。


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // 錯誤:無法變更 final 欄位
}

沒有無參數建構子

record 類別始終只有主要建構子,必須接收所有元件的值。無法在未提供所有資料的情況下建立 record


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // 錯誤:沒有無參數建構子

沒有非靜態初始區塊

record 類別不能包含非靜態初始區塊(也就是寫在方法之外的大括號初始區塊):


public record User(String login) {
    // { /* ... */ } // 錯誤:禁止非靜態初始區塊
}

繼承方面的限制

record 類別不能顯式繼承其他類別(除了作為所有 record 基底類別且對我們隱藏的 java.lang.Record)。但可以實作介面!


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("列印書籍: " + title);
    }
}

不適合複雜的商業邏輯

record 著重在資料,而非行為。若物件具有複雜邏輯、可變狀態、「生命週期」或大量相依性——record 幫不上忙,改用一般類別更合適。

3. 何時該使用 record 類別?

  • DTO (Data Transfer Object): 在應用層、服務、微服務或 REST 控制器之間傳遞不可變資料(例如 JSON 回應)。
  • Value Object: 僅由其值所定義的物件。
  • 集合中的鍵與值: 當需要正確實作 equalshashCode(例如用於 HashMapSet)。
  • 計算結果: 當需要從方法一次回傳多個值(例如,record Pair<T, U>(T first, U second))。

範例:REST 控制器的 DTO


public record UserDto(String login, String email) { }

現在可以放心從控制器回傳此型別的物件,而不必擔心有人會改動其欄位。

範例:HashMap 的鍵


public record Point(int x, int y) { }

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// 一切運作正常:equals 和 hashCode 已經實作好了!

4. 何時不該使用 record 類別

  • 可變狀態: 只要有任一欄位需要在物件建立後變更。
  • 複雜邏輯: 物件具有複雜行為、許多方法、以及包含可變狀態的巢狀物件。
  • 繼承: 需要類別階層、抽象基底類別、或方法覆寫。
  • 商業邏輯的實體: 例如存在於資料庫且具有唯一識別碼的物件。

範例:需要一般類別的情況


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

這裡明顯會改變物件狀態——record 不適合。

5. 實務範例:在 record 與 class 之間做選擇

範例 1:record——完美選擇


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • 矩形僅由寬與高決定。
  • 建立後無需變更這些值。
  • 可加入實用方法 area()
  • 其餘由 Java 替您處理。

範例 2:class——較佳選擇


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    public int area() { return width * height; }
}

建立後還需要調整矩形尺寸?使用一般類別。

6. 使用 record 類別時的常見錯誤

錯誤一:嘗試新增非靜態欄位。
record 類別不允許在元件清單之外宣告非靜態欄位。若嘗試這麼做——編譯器會報錯。例如:


public record City(String name) {
    // int population; // 錯誤!
}

錯誤二:想要加入 setter。
record 不支援元件的 setter。任何在物件建立後嘗試變更欄位值的行為——都是編譯錯誤。

錯誤三:嘗試繼承 record 或讓 record 繼承其他類別。
record 一律為 final。不能從 record 繼承,record 也不能繼承其他類別(隱含的 java.lang.Record 除外)。

錯誤四:將 record 用於可變物件。
如果計畫在建立後變更物件狀態——record 不適用!請使用一般類別。

錯誤五:忽略建構子的限制。
record 類別必須具有接受所有元件值的建構子。不存在無參數建構子!

1
問卷/小測驗
Record 類別,等級 22,課堂 4
未開放
Record 類別
Record 類別
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION