在业务系统开发中,只读的业务实体类可以避免数据在传递过程中被意外修改,减少潜在的bug。很多开发者首先会想到只给实体类定义Getter方法,不定义Setter方法来实现这类需求,接下来我们就详细分析这种实现方式的相关要点。

仅定义Getter方法的局限性
如果实体类的属性都是基本数据类型或者不可变的引用类型(比如String、Integer),只定义Getter方法确实能实现只读效果。但如果属性是可变的引用类型,比如自定义对象、集合,仅定义Getter就会存在风险。
比如下面的实体类,只定义了Getter方法:
public class User {
private String name;
private List<String> roles;
public User(String name, List<String> roles) {
this.name = name;
this.roles = roles;
}
public String getName() {
return name;
}
public List<String> getRoles() {
return roles;
}
}
外部调用getRoles()之后,可以直接修改返回的List内容,因为List本身是可变的,这就破坏了实体的不可变性。
完整的只读业务实体类实现方案
1. 属性使用final修饰
将实体类的属性用final修饰,保证属性只能在构造器中赋值,后续无法被修改引用。
2. 构造器初始化所有属性
不提供无参构造器,通过有参构造器完成所有属性的初始化,避免属性出现未赋值的情况。
3. 处理可变引用类型的返回
如果属性是可变的引用类型,Getter方法返回时需要返回副本,而不是直接返回原引用。对于集合类型,可以返回不可修改的集合。
4. 类本身用final修饰
避免实体类被继承,防止子类修改其行为,进一步保证不可变性。
完整代码示例
下面是一个符合要求的只读用户实体类实现:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class ReadOnlyUser {
// 属性用final修饰,保证引用不可变
private final String name;
private final int age;
private final List<String> roles;
// 有参构造器初始化所有属性
public ReadOnlyUser(String name, int age, List<String> roles) {
this.name = name;
this.age = age;
// 对传入的可变集合做副本存储,避免外部修改影响内部属性
this.roles = new ArrayList<>(roles);
}
// 仅提供Getter方法,无Setter方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getRoles() {
// 返回不可修改的集合副本,防止外部修改集合内容
return Collections.unmodifiableList(new ArrayList<>(roles));
}
}
使用示例验证
我们编写测试代码验证这个实体类的只读特性:
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> roleList = Arrays.asList("admin", "user");
ReadOnlyUser user = new ReadOnlyUser("张三", 20, roleList);
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getRoles());
// 尝试修改返回的roles集合,会抛出UnsupportedOperationException
try {
user.getRoles().add("guest");
} catch (UnsupportedOperationException e) {
System.out.println("无法修改只读实体的集合属性");
}
}
}
运行上述代码可以发现,尝试修改getRoles()返回的集合时会抛出异常,说明实体类的不可变性得到了保证。
注意事项
- 如果实体类包含自定义的引用类型属性,该引用类型本身也需要设计成不可变的,或者在Getter中返回其深拷贝副本。
- 不要提供任何可以修改内部属性的方法,包括修改集合、修改引用对象的方法。
- 如果实体类需要序列化,要注意
final属性的序列化兼容性问题,避免反序列化时出现问题。
只读实体类的核心是保证内部状态在任何情况下都不会被外部修改,仅定义Getter只是第一步,需要配合其他规则才能完全实现不可变性。