CodeGym /課程 /JAVA 25 SELF /具有方法的 Record

具有方法的 Record

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

1. 擴充 record 類別:額外的方法

可以在 record 中新增方法嗎?

當然可以!Record 就像一間已裝潢好的公寓:牆面與地板都已完成,不能更改,但擺設傢俱則是自由的。你可以在 record 內宣告一般方法、靜態方法,甚至儲存常數。這表示商業邏輯不一定要放到額外的「工具」類別中 —— 也可以乾淨地直接內嵌在 record 本身。

範例:計算兩點距離的方法

假設我們有一個平面座標點的 record


public record Point(int x, int y) {
    // 額外的方法
    public double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

現在可以這樣做:


Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0

如你所見,record 類別可以用自己的方法來「擴充」—— 這非常實用!

範例:靜態方法


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

    public static Rectangle square(int size) {
        return new Rectangle(size, size);
    }
}

現在可以用一次呼叫建立「正方形」:


Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25

2. 精簡建構子與資料驗證

為什麼需要「精簡」建構子?

record 的典型(canonical)建構子會自動產生並將參數指派給欄位。但有時我們想加入輸入資料的檢查(例如:禁止負的座標)。

在一般類別中我們會這樣寫:


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        if (x < 0 || y < 0) throw new IllegalArgumentException();
        this.x = x;
        this.y = y;
    }
    // ...
}

record 類別中可以宣告精簡建構子 —— 不需要重複參數清單,也不需要顯式指派欄位(編譯器會替我們完成)。

精簡建構子的語法


public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("Coordinates must be non-negative");
        }
        // 不需要寫:this.x = x; this.y = y;
    }
}
  • 建構子的參數會自動與 record 的元件對應。
  • this.x = xthis.y = y 的指派會在建構子主體執行完畢(或成功離開)後由編譯器自動完成。
  • 如果拋出例外,物件就不會被建立。

帶有檢查的例子


Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // 會拋出 IllegalArgumentException!

能否宣告「一般」建構子?

可以!如果需要建立帶有不同參數列表或額外邏輯的建構子,請明確宣告:


public record Range(int from, int to) {
    public Range(int size) {
        this(0, size); // 呼叫主要建構子
    }
}

3. record 類別的限制

與一般類別不同,record 類別有一系列限制。記住這點,以免對編譯錯誤感到意外。

只有元件 —— 不能新增額外的非靜態欄位

record 類別中不能宣告新的非靜態欄位:


public record Person(String name, int age) {
    // int id; // 編譯錯誤!不可新增非靜態欄位。
}

可以宣告靜態欄位和方法:


public record Person(String name, int age) {
    public static final String SPECIES = "Homo sapiens";
}

Record 總是 final

Record 類別不能作為父類別(不可被繼承),也不能顯式繼承其他類別(除了隱式繼承自 java.lang.Record)。這表示 record 類別永遠是「終點」結構。


public record User(String login) { }

// public class Admin extends User {} // 錯誤:不可繼承自 record!

可以實作介面

Record 類別可以實作介面:


public interface Printable {
    void print();
}

public record Invoice(int amount) implements Printable {
    @Override
    public void print() {
        System.out.println("金額:" + amount);
    }
}

4. 範例:在實際任務中的進階 record

我們來看幾個實用範例,說明額外的方法與精簡建構子如何讓 record 類別更有用。

具有可計算方法的 record


public record Circle(double x, double y, double radius) {
    public double area() {
        return Math.PI * radius * radius;
    }

    public double distanceTo(Circle other) {
        double dx = x - other.x;
        double dy = y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

具有驗證的 record


public record Email(String value) {
    public Email {
        if (value == null || !value.contains("@")) {
            throw new IllegalArgumentException("不正確的 email:" + value);
        }
    }
}

現在無法建立不正確的 email:


Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // 會拋出 IllegalArgumentException

具有附加靜態方法的 record


public record Temperature(double celsius) {
    public static Temperature fromFahrenheit(double fahrenheit) {
        return new Temperature((fahrenheit - 32) * 5 / 9);
    }

    public double toFahrenheit() {
        return celsius * 9 / 5 + 32;
    }
}

用法:


Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6

5. 精簡建構子:細節與限制

何時該使用精簡建構子?

  • 當你需要檢查資料正確性(驗證)。
  • 當你需要在儲存前調整數值(例如將數字四捨五入或將字串轉為大寫)。
  • 當你想避免重複列出參數清單。

注意事項

  • 在精簡建構子中不可顯式指派元件值(this.x = ...),否則會導致編譯錯誤;因為編譯器會在建構子主體執行後自動完成指派。
  • 在精簡建構子中不可修改參數名稱 —— 它們必須與 record 的元件名稱一致。

範例:自動四捨五入


public record Money(double amount) {
    public Money {
        amount = Math.round(amount * 100) / 100.0; // 四捨五入到小數點後兩位
    }
}

6. 實作:擴充教學應用程式

假設我們在教學應用程式中實作銀行交易。設有一個 record 類別 Transaction,用來保存金額、匯款人與收款人。


public record Transaction(String from, String to, double amount) {
    public Transaction {
        if (amount <= 0) throw new IllegalArgumentException("金額必須為正數");
        if (from == null || to == null) throw new IllegalArgumentException("欄位不能為 null");
    }

    public String description() {
        return String.format("轉帳 %.2f,從 %s 到 %s", amount, from, to);
    }
}

用法:


Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // 轉帳 150.00,從 Alice 到 Bob

嘗試建立不正確的交易會造成錯誤:


Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException

表格:record 類別中能做與不能做的事

在 record 類別中可以 在 record 類別中不可以
一般方法 新的非靜態欄位
靜態方法與欄位 繼承其他類別
實作介面 作為其他類別的超類別
精簡與一般建構子 在建立後修改元件
覆寫方法 使用 setter

7. 使用具非標準主體的 record 類別時的常見錯誤

錯誤 № 1:嘗試加入非靜態欄位。
新手常會嘗試在 record 類別中加入「內部邏輯的另一個欄位」—— 例如計數器或快取。這不會成功:編譯器會立刻報錯。如果你需要保存額外狀態,很可能 record 類別並不適合。

錯誤 № 2:在精簡建構子中遺漏驗證。
如果你希望物件永遠是有效的,請在精簡建構子中進行檢查。不要指望「使用者自己不會亂輸入」。

錯誤 № 3:嘗試在建立後修改元件。
record 類別的欄位是 final —— 不能直接修改,也不能透過方法修改。如果你需要可變的結構,請使用一般類別。

錯誤 № 4:在方法與建構子中重複邏輯。
有時會把檢查邏輯與計算邏輯同時放在方法與建構子裡重複一遍。最好將所有驗證集中在建構子中,而讓方法保留「純」商業邏輯。

錯誤 № 5:忽略繼承限制。
Record 類別永遠是 final —— 不能從它繼承。如果你在設計需要子類別的階層,請使用一般類別。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION