不可变类:对象一旦创建完成,所有成员状态不能再修改,
String、Integer、BigDecimal都是典型代表。 核心思想:禁止外部修改 + 内部也不提供修改途径
✅ 实现 5 条核心规则(面试标准答法)
类使用
final修饰 防止被子类继承,子类重写方法破坏不可变性。public final class User { ... }所有成员变量私有化
private,并且final修饰private:外部无法直接访问final:赋值一次后引用不可重新指向别的对象
⚠️ 注意:
final只是引用不可变,如果成员是可变对象(ArrayList、Date、自定义对象),对象内部字段依然可以改!不提供任何
setter修改方法 没有任何方法可以修改成员。构造器初始化所有字段,并且做【防御拷贝】(重点难点) 如果字段是可变引用类型,构造的时候不能直接赋值外部传入的引用,要拷贝一份。
getter 方法返回时,也要做防御拷贝 返回可变对象时,不能直接返回原引用,防止外部拿到引用修改内部数据。
📌 完整示例代码(区分基本类型 & 可变引用类型)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* 不可变类示例
*/
public final class Person {
// 基本类型 / 不可变类型:直接final即可
private final long id;
private final String name;
// 可变引用类型:Date、List,必须防御拷贝
private final Date birthDay;
private final List<String> tags;
// 构造器:一次性初始化全部字段
public Person(long id, String name, Date birthDay, List<String> tags) {
this.id = id;
this.name = name;
// ✅ 防御拷贝:不要直接 this.birthDay = birthDay
this.birthDay = birthDay == null ? null : new Date(birthDay.getTime());
// ✅ List防御拷贝 + 包装为不可变集合
if (tags == null) {
this.tags = Collections.emptyList();
} else {
this.tags = Collections.unmodifiableList(new ArrayList<>(tags));
}
}
// 只有getter,没有setter
public long getId() {
return id;
}
public String getName() {
return name;
}
// Date是可变对象,get的时候也要拷贝一份出去
public Date getBirthDay() {
return birthDay == null ? null : new Date(birthDay.getTime());
}
// 返回不可变List,外部无法add/remove
public List<String> getTags() {
return tags;
}
}
错误反面示例(不做防御拷贝,不可变性被破坏)
// ❌ 危险写法
public Person(Date birthDay){
this.birthDay = birthDay;
}
// 外部调用:
Date d = new Date();
Person p = new Person(d);
d.setTime(99999999); // 直接修改了Person内部的birthDay!
📎 补充:其他可选优化点
构造器私有 + 静态工厂方法(推荐) 可以不对外暴露构造函数,用
of()创建实例,方便缓存、复用对象(类似Integer.valueOf缓存)public final class Person { private Person(...) { ... } public static Person of(long id, String name, Date birthDay, List<String> tags){ return new Person(id, name, birthDay, tags); } }重写
equals()、hashCode()、toString()不可变对象非常适合作为HashMap key,一定要正确重写。序列化:如果实现 Serializable 需要保证反序列化不会创建可变实例,必要时重写
readResolve()。
🆚 容易踩坑总结
表格
✨ Java 新方案:Record(Java 16+)
Java 16 提供 record 语法,自动生成极简不可变载体类,默认:
类隐式 final
成员隐式 final
自动构造器、equals、hashCode、toString、getter
但是!record 不会自动对可变引用做防御拷贝! 这是高频面试坑!
// Record 写法,适合纯数据载体
public record UserRecord(Long id, String name, Date birth) {}
⚠️ 上面这个
UserRecord不是安全不可变类!因为Date birth是可变对象,构造直接复用引用,必须手动在紧凑构造器里防御拷贝:
public record UserRecord(Long id, String name, Date birth) {
// 紧凑构造器
public UserRecord {
birth = birth == null ? null : new Date(birth.getTime());
}
}
📌 和 String 不可变性关联
String 底层(Java9+)是 private final byte[] value; String 符合不可变类规范:类 final、无 setter、修改操作 (substring /replace)都是返回新对象,不修改自身。
本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。
原文链接
欢迎访问