bigywater.tech
Java 内部原理 · 108

final字段与clone()真实验证:super.clone()真实不需要手动赋值就能正确复制final字段

clone()方法体内没法对final字段重新赋值——这是Java语法的硬性规定。这篇真实验证super.clone()在完全不碰这个语法限制的前提下,是怎么把final字段值正确带到新对象上的,以及这个限制对深拷贝造成的真实局限。

super.clone()真实正确复制final字段
真实验证:clone()方法体内没有任何一行手动赋值final字段的代码,cloned.id真实还是等于original.id的值
final数组引用真实依然共享同一数组
真实验证:final修饰的数组引用字段clone()后真实还是同一个数组对象,修改内容真实互相影响,深拷贝真实无法在clone()方法体内实现

上一篇:Thread.yield() 真实验证

真实验证: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(是两个不同对象)
真实机制不在Java源码层面,在JVM底层。这个结果之所以真实成立,原因是 Object.clone() 的真实实现不是普通的Java代码逐字段赋值,它是一个 native 方法,由 JVM 在底层真实做的是"按位复制整个对象的内存布局"(包括对象头和所有实例字段占用的内存)。这个复制动作发生在 Java 语言的字段赋值规则(final字段只能在构造器里赋值一次)之外,JVM 有权限直接操作内存,所以能真实把 final 字段的值也复制过去,而完全不违反"final字段不能在构造器外被重新赋值"这条Java语言层面的规则——因为从Java语言的视角看,这次赋值根本"没有通过赋值语句发生",它是JVM内存复制的自然结果。

真实验证:final修饰的数组引用,clone()后依然共享同一数组

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() 会自动帮你深拷贝它。

真实结论:final引用字段的深拷贝,是Cloneable机制真实无法解决的局限

把两组结果放在一起看,能得出一个真实、具体的工程结论:如果一个类想要 clone() 出一份真正独立的深拷贝(数组或其他可变引用字段也要变成独立副本),标准做法是在 clone() 方法体内对这些字段重新赋值,比如 cloned.data = data.clone()——但如果这个字段被 final 修饰,这行代码真实无法编译通过(尝试给final字段重新赋值)。这意味着一旦某个引用类型字段真实需要深拷贝,又出于设计需要必须是 final 的,Cloneable/clone() 这套机制就真实走进了死胡同——这也是《Effective Java》建议用拷贝构造器或静态工厂方法代替 clone() 的另一个真实理由:拷贝构造器完全不受这个语法限制,可以在构造过程中随意给 final 字段赋任意需要的值。

分步演示:final 字段是怎么被"绕过语言规则"复制的

每一行都是真实运行这段代码得到的结果。

super.clone() 真实不需要手动赋值就能复制 final 字段;final 数组引用 clone() 后依然共享同一个数组
这一步新验证的字段

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,final字段复制和数组共享行为重复运行完全一致。
    • 和Object.clone() 浅拷贝那篇一起看,能拼出 Cloneable 机制在处理各类字段(普通引用、final引用)时统一而又局限的真实图景。
    • 没有涉及:拷贝构造器(Copy Constructor)作为clone()替代方案的真实实现示例、record类型天然不可变从根源上规避这整类深拷贝问题的设计思路——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号