怎么利用 Object.clone() 实现对象的浅拷贝并理解其 Cloneable 接口的要求
怎么利用 Object.clone() 实现对象的浅拷贝并理解其 Cloneable 接口的要求

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
Object.clone() 为什么不能直接调用
很多开发者第一次尝试调用 clone() 时都会碰壁,原因其实有两点。首先,这个方法在 Object 类中被声明为 protected,这意味着外部代码无法直接访问,除非当前类自己把它“放出来”。但更关键、也更容易被忽略的一点在于,Object 的默认实现内置了一个运行时检查:它会严格审视调用者是否实现了 Cloneable 这个标记接口。如果没有,它可不会客气地返回个 null,而是直接抛出 CloneNotSupportedException。
所以,你常常会看到这样的场景:代码 myObj.clone() 在编译期一帆风顺,一到运行时就报出异常。问题的根源,十有八九是忘了声明那个看似空无一物的接口,或者漏掉了重写方法那一步。
- 第一步,必须让类实现
Cloneable接口。别小看这个空接口,它是 JVM 识别克隆资格的“通行证”。 - 第二步,必须重写
clone()方法,并将其访问权限提升为public。 - 第三步,在重写时,建议调用
super.clone()来启动拷贝流程,而不是手动进行 new 对象和字段赋值,否则就失去了浅拷贝的原本语义。
浅拷贝的实际效果和典型陷阱
那么,默认的 Object.clone() 究竟做了什么呢?它执行的是标准的浅拷贝:只复制对象自身的各个字段。对于基本类型字段,值被完整复制;但对于引用类型字段,复制的仅仅是那个引用地址,而不是引用所指向的堆内存中的那个实际对象。这就导致了一个核心结果:原始对象和它的克隆体,会共享内部的可变对象。
这种特性决定了它的适用场景:当你的类字段全是基本类型,或者包含的是像 String、Integer 这样的不可变对象时,浅拷贝完全够用。当然,如果你本身就希望克隆对象共享某些内部状态,那它更是绝佳选择。
不过,陷阱也往往藏在这里:
- 如果类里有个
ArrayList字段,克隆之后,两个对象的list字段指向的是内存中的同一个列表实例。此时,修改任何一个对象列表里的内容,另一个对象会立刻“感知”到变化。 - 如果字段是自定义的可变类,比如
Person里有个Address address,而Address类自己没有实现深拷贝逻辑,那么这个address对象同样会被共享。 - 另外,
Cloneable接口本身并不强制要求你重写clone()方法,但如果你不重写,这个类就等于没有对外提供真正的克隆能力。
正确实现 Cloneable 的最小可行代码
理论说再多,不如看一段最简实现。下面是一个完整且正确的示例,请特别注意访问修饰符、异常声明和调用链:
public class User implements Cloneable {
private String name;
private int age;
private ArrayList tags;
@Override
public User clone() {
try {
return (User) super.clone(); // 浅拷贝本体
} catch (CloneNotSupportedException e) {
throw new AssertionError("Cloneable interface is implemented, this should never happen", e);
}
}
}
这段代码说明了几个要点:
super.clone()已经完成了所有字段的逐位复制,包括tags这个引用本身。注意,复制的是引用,而不是tags列表里的元素。- 如果需要实现深拷贝,例如希望
tags列表也被完整复制一份,就必须在clone()方法里手动处理,比如用new ArrayList<>(this.tags)来创建一个新的列表实例。 - 切记,不要在
clone()方法内部去调用类的构造函数,那样就完全背离了克隆的语义。
Cloneable 接口没有方法却必不可少的原因
你可能会好奇,一个没有任何方法的接口,凭什么如此重要?答案是,Cloneable 是 JVM 层面的一个“契约标记”。Object.clone() 在底层实现中,会通过 getClass().isInterface() 等机制检查类的标记位,这个检查发生在运行时,而非编译期。因此,即使你在类里完整地写了一个 public Object clone() 方法,但只要省略了 implements Cloneable 这句声明,运行时依然会抛出异常。
再来看看它的性能与兼容性影响:
- 浅拷贝的性能开销极小,远胜于通过序列化或反射机制实现的拷贝,在性能敏感的场景下优势明显。
- 但是,需要注意一个重要的风向变化:在 JDK 17 及以后的版本中,
Cloneable已被标记为@Deprecated(forRemoval = true)。这传递出一个清晰的信号:官方更倾向于使用构造器、记录类(record),或者copyOf()风格的 API 来作为替代方案。 - 尽管如此,许多第三方库(例如 Apache Commons Lang 中的
BeanUtils.cloneBean())在底层可能仍然依赖这套机制,因此在兼容性考量上仍需留意。
最后,还有一个容易被忽略的细节:即便一个类的所有字段都是不可变的,只要它声明了 Cloneable 并重写了 clone(),就必须考虑继承链的安全。如果继承链中某个父类没有正确重写 clone(),那么子类在调用 super.clone() 时,仍然可能遭遇失败。这才是设计一个可克隆类时需要通盘考虑的关键所在。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何优化Apache2响应速度
Apache2响应速度优化实操指南 想让你的Apache2服务器跑得更快?这事儿其实有章可循。下面这份实操指南,将从基础到进阶,帮你系统地提升响应速度。记住,所有优化都建立在不变动核心业务逻辑和架构的前提下。 一 基础与系统层面优化 优化得从地基开始。系统层面的几个关键设置,往往能以小成本换来大收益
git多人协作的工作流程【汇总】
多人协作必须禁用直接 push 到 main 分支:PR MR 流程是保障代码质量、自动化测试与冲突预判的核心机制;最佳实践包括语义化分支命名、启用分支保护规则,并规范 rebase 与 merge 的使用场景。 多人协作时,为什么禁止直接 push 到 main 分支? 直接向主分支推送代码,表面
CentOS上如何升级PHPStorm到最新版本
在 CentOS 上升级 PhpStorm 的可选方案 说到在 CentOS 上升级 PhpStorm,其实路径很清晰。核心原则是:优先使用内置更新或 JetBrains Toolbox App 这类自动管理工具,其次才是手动下载安装包覆盖升级。下面,就按推荐顺序,把每种方式的操作步骤和关键要点给你
Atom如何设置自动保存?Atom自动保存功能开启教程
Atom如何设置自动保存?Atom自动保存功能开启教程 如果你还在为Atom的自动保存功能头疼,那很可能踩中了几个常见的“坑”。从1 27版本开始,autosa ve功能已经作为核心特性内置,不再依赖插件。但问题也随之而来:为什么设置了却不见效?答案往往藏在版本、配置层级,或者那些本该被清理的旧插件
如何在CentOS上备份PHPStorm的配置文件
在 CentOS 上备份 PhpStorm 配置文件:完整指南与最佳实践 一、备份前的准备工作 在开始备份 PhpStorm 配置之前,充分的准备工作至关重要。这能有效保障备份数据的完整性与安全性,避免因操作不当导致配置丢失或损坏。 彻底关闭 PhpStorm 应用程序:这是首要且必须的步骤。确保
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

