防御性拷贝是在对外暴露或接收外部可变对象时主动创建独立副本的操作,旨在保护类的封装性和内部状态不变性;它通过隔离引用防止外部修改影响内部字段,适用于构造函数接收、getter返回及持有可变对象等场景,需依对象可变性选择浅拷贝或深拷贝,并优先采用不可变类型替代。

什么是防御性拷贝
防御性拷贝是在对外暴露或接收外部可变对象时,主动创建一份独立副本的操作。它不是为了性能优化,而是为了保护类的封装性和内部状态不变性。核心逻辑很简单:只要对方拿到的是你内部对象的引用,就等于给了他修改你私有字段的钥匙——哪怕他本意是好的。
哪些场景必须做防御性拷贝
以下三类情况不拷贝就容易出问题:
- 构造函数接收外部传入的可变对象(如 Date、ArrayList、int[]),直接赋值给成员变量
- getter 方法直接返回内部可变集合或可变对象引用(比如 return items)
- 内部持有可变对象且该对象本身可能被外部修改(例如 User 类里包含 Occupation 实例)
怎么正确做防御性拷贝
关键看对象是否嵌套可变:
- 若字段是 String、Integer 等不可变类型,浅拷贝足够(如 new ArrayList(original))
- 若字段是可变对象(如自定义的 Date 或 Person),需递归深拷贝(如 new Occupation(other.oc))
- 数组字段不能用赋值,要用 Arrays.copyOf() 或 clone()
- 不要依赖 Collections.unmodifiableList 来“假装安全”——它只挡调用方,不防底层被别处改
常见误区与提醒
很多人以为加了 final 就万事大吉,其实 final 只保证引用不重赋值,不阻止对象内容被修改。
还有人把防御性拷贝写在 getter 里,看似安全,但每次调用都新建对象,可能引发不必要的 GC 压力;更推荐在构造和设值阶段就完成隔离。
从 Java 8 起,优先使用 Instant、LocalDateTime 替代 Date,用 ImmutableList 替代手动维护的不可变包装——不可变设计比防御性拷贝更彻底、更省心。










