Java 作者谈克隆方法的实现

简介: 今天在用 sonar 审核代码, 偶然看到下面的提示

今天在用 sonar 审核代码, 偶然看到下面的提示:

4_C}QPL89`_XFTBCL2991XO.png

关于这个的提示大意是:

“克隆”不应该被覆盖, 属坏味道, 阻断型错误约书亚•布洛赫表示,许多人在 Java 中对 clone 方法 和 Cloneable 接口存在误解,很大程度上是因为重写 clone 方法的规则很棘手, 且出错难以纠正。Object 的 clone 方法非常棘手。它基于属性复制,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数创建的不变量。多年来,在 Sun 公司内外都存在许多错误,这源于这样一个事实,即如果你只是反复调用 super.clone 直到克隆了一个对象,那么你就拥有了一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。

所以, 应该使用复制构造函数或复制工厂。

clone 无论是否实现 Cloneable 接口,此规则在被覆盖时都会引发问题。

不合规的代码示例

public class MyClass {
  // ...
  public Object clone() { // Noncompliant
    //...
  }
}

合规解决方案

public class MyClass {
  // ...
  MyClass (MyClass source) {
    //...
  }
}


参阅

《复制构造函数与克隆》

也可以参阅

S2157 - “Cloneables”应该实现“克隆”

S1182 - 覆盖“clone”的类应为“Cloneable”并调用“super.clone()”

下面为引文翻译

Josh Bloch 谈设计

与《Effective Java》作者的对话,Josh Bloch

作者 Bill Venners

首次在JavaWorld上发表,2002年1月4日


复制构造函数与克隆


Bill Venners: 在你的书中,你建议使用复制构造函数而不是实现Cloneable和编写clone。你能详细说明吗?

Josh Bloch:如果你已经阅读了我的书中关于克隆的章节,特别是如果你看得仔细的话,你就会知道我认为克隆已经完全坏掉的东西。有一些设计缺陷,其中最大的一个是 Cloneable 接口没有 clone方法。这意味着它根本不起作用:实现了 Cloneable 接口并不说明你可以用它做什么。相反,它说明了内部可能做些什么。它说如果通过super.clone 反复调用它最终调用 Object 的 clone 方法,这个方法将返回原始的属性副本。


但它没有说明你可以用一个实现 Cloneable 接口的对象做什么,这意味着你不能做多态 clone 操作。如果我有一个 Cloneable 数组,你会认为我可以运行该数组并克隆每个元素以制作数组的深层副本,但不能。你不能强制转换对象为 Cloneable 接口并调用 clone 方法,因为 Cloneable 没有public clone 方法,Object 类也没有。如果您尝试强制转换 Cloneable 并调用该 clone 方法,编译器会说您正在尝试在对象上调用受保护的clone方法。


事实的真相是,您不通过实施 Cloneable 和提供 clone 除复制能力之外的公共方法为您的客户提供任何能力。如果您提供具有不同名称的copy操作, 怎么也不次于去实现 Cloneable 接口。这基本上就是你用复制构造函数做的事情。复制构造方法有几个优点,我在本书中有讨论。一个很大的优点是可以使副本具有与原始副本不同的实现。例如,您可以将一个 LinkedList 复制到 ArrayList。


Object 的 clone 方法是非常棘手的。它基于属性复制,而且是“超语言”。它创建一个对象而不调用构造函数。无法保证它保留构造函数建立的不变量。多年来,在Sun内外存在许多错误,这源于这样一个事实,即如果你只是super.clone 反复调用链直到你克隆了一个对象,那么你就拥有了一个浅层的对象副本。克隆通常与正在克隆的对象共享状态。如果该状态是可变的,则您没有两个独立的对象。如果您修改一个,另一个也会更改。突然之间,你会得到随机行为。


我使用的东西很少实现 Cloneable。我经常提供实现类的 clone 公共方法,仅是因为人们期望有。我没有抽象类实现 Cloneable,也没有接口扩展它,因为我不会将实现的负担 Cloneable 放在扩展(或实现)抽象类(或接口)的所有类上。这是一个真正的负担,几乎没有什么好处。


Doug Lea 走得更远。他告诉我 clone 除了复制数组之外他不再使用了。您应该使用 clone 复制数组,因为这通常是最快的方法。但 Doug 的类根本就不再实施 Cloneable了。他放弃了。而且我认为这并非不合理。


这是一个耻辱, Cloneable 接口坏掉了,但它发生了。最初的 Java API在紧迫的期限内完成,以满足市场窗口收紧的需求。最初的 Java 团队做了不可思议的工作,但并非所有的 API 都是完美的。 Cloneable 是一个弱点,我认为人们应该意识到它的局限性。

clone

相关文章
|
3月前
|
消息中间件 Java Kafka
在Java中实现分布式事务的常用框架和方法
总之,选择合适的分布式事务框架和方法需要综合考虑业务需求、性能、复杂度等因素。不同的框架和方法都有其特点和适用场景,需要根据具体情况进行评估和选择。同时,随着技术的不断发展,分布式事务的解决方案也在不断更新和完善,以更好地满足业务的需求。你还可以进一步深入研究和了解这些框架和方法,以便在实际应用中更好地实现分布式事务管理。
|
3月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
109 9
|
21天前
|
存储 Java 索引
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
|
17天前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
13天前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
46 9
|
18天前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
48 12
|
19天前
|
算法 Java API
Java 方法注释:规范、实用和高质量的写法
本文深入探讨了如何编写高质量的 Java 方法注释
46 11
|
19天前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
37 5
|
3月前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
69 4
|
3月前
|
Java
深入探讨Java中的中断机制:INTERRUPTED和ISINTERRUPTED方法详解
在Java多线程编程中,中断机制是协调线程行为的重要手段。了解和正确使用中断机制对于编写高效、可靠的并发程序至关重要。本文将深入探讨Java中的`Thread.interrupted()`和`Thread.isInterrupted()`方法的区别及其应用场景。
102 4