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 构造器会自动生成并把参数赋值给字段。但有时我们希望添加对输入数据的校验(例如禁止负坐标)。

在普通类中我们会这样写:


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. 示例:在实际场景中扩展 records

我们来看几个实际示例,展示额外方法和紧凑构造器如何让 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. 实践:完善教学用应用

假设我们在做一个教学用的银行业务应用。我们有一个 recordTransaction,用于保存金额、转出方和接收方。


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