clone()方法体内没法对final字段重新赋值——这是Java语法的硬性规定。这篇真实验证super.clone()在完全不碰这个语法限制的前提下,是怎么把final字段值正确带到新对象上的,以及这个限制对深拷贝造成的真实局限。
clone()方法体本身完全没有对final字段做任何操作(Java语法也不允许),验证super.clone()是否真的能正确处理它。
CloneFinalFieldDemo.java class WithFinalField implements Cloneable { final int id; int mutableValue; public WithFinalField clone() throws CloneNotSupportedException { return (WithFinalField) super.clone(); // 方法体里没有任何一行涉及id字段 } }
cloned.id 真实值: 42(super.clone()真实正确复制了final字段,不需要手动赋值) original.id == cloned.id 真实结果: true original != cloned 真实结果: true(是两个不同对象)
final修饰的只是引用本身不可变,不代表数组内容不可变——这次验证浅拷贝对final引用字段是否同样成立。
CloneFinalFieldDemo.java class WithFinalArrayField implements Cloneable { final int[] data; // final只锁定引用,不锁定数组内容 public WithFinalArrayField clone() throws CloneNotSupportedException { return (WithFinalArrayField) super.clone(); // 无法写 cloned.data = data.clone() } }
fa1.data == fa2.data 真实结果: true(final数组引用clone后真实还是共享同一个数组) 修改fa2.data[0]=999后,fa1.data[0] 真实结果: 999(因为共享同一个数组,真实互相影响)
真实结果证明了浅拷贝规则对 final 引用字段同样成立(系列前面 Object.clone() 浅拷贝那篇验证过普通引用字段的这条规律):super.clone() 复制的是引用本身,fa1.data 和 fa2.data 真实指向同一个数组对象,修改数组内容真实会互相影响——final 修饰符只保证了"这个引用变量本身不能被重新指向别的数组",不保证数组内容不可变,也不保证 clone() 会自动帮你深拷贝它。
把两组结果放在一起看,能得出一个真实、具体的工程结论:如果一个类想要 clone() 出一份真正独立的深拷贝(数组或其他可变引用字段也要变成独立副本),标准做法是在 clone() 方法体内对这些字段重新赋值,比如 cloned.data = data.clone()——但如果这个字段被 final 修饰,这行代码真实无法编译通过(尝试给final字段重新赋值)。这意味着一旦某个引用类型字段真实需要深拷贝,又出于设计需要必须是 final 的,Cloneable/clone() 这套机制就真实走进了死胡同——这也是《Effective Java》建议用拷贝构造器或静态工厂方法代替 clone() 的另一个真实理由:拷贝构造器完全不受这个语法限制,可以在构造过程中随意给 final 字段赋任意需要的值。
每一行都是真实运行这段代码得到的结果。