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 类不能显式继承其他类(除了 java.lang.Record,它对我们是隐藏的,作为所有 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 类时的常见错误

错误 1:尝试添加非静态字段。
record 类不允许在组件列表之外声明非静态字段。如果尝试这样做,编译器会报错。例如:


public record City(String name) {
    // int population; // 错误!
}

错误 2:想要添加 setter。
record 不支持为组件提供 setter。对象创建后尝试修改字段值会导致编译错误。

错误 3:尝试继承 record 或让其他类从 record 继承。
record 始终是 final。不能从 record 继承,record 也不能继承其他类(除了隐藏的 java.lang.Record)。

错误 4:将 record 用于可变对象。
如果你计划在对象创建后修改其状态,record 并不适合!请使用普通类。

错误 5:忽略构造器限制。
record 类必须拥有一个接收所有组件值的构造器。不存在无参构造器!

1
任务
JAVA 25 SELF, 第 22 级, 课程 4
已锁定
家庭图书馆目录 📚
家庭图书馆目录 📚
1
任务
JAVA 25 SELF, 第 22 级, 课程 4
已锁定
检查地图坐标匹配 🗺️
检查地图坐标匹配 🗺️
1
任务
JAVA 25 SELF, 第 22 级, 课程 4
已锁定
打印文档内容 📄
打印文档内容 📄
1
任务
JAVA 25 SELF, 第 22 级, 课程 4
已锁定
可变与不可变用户配置文件的比较 🧑‍🤝‍🧑
可变与不可变用户配置文件的比较 🧑‍🤝‍🧑
1
调查/小测验
Record 类第 22 级,课程 4
不可用
Record 类
Record 类
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION