- 浏览: 403261 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
c253898303:
求和的时候说是调用store的基础方法,这个能重写吗?如果可以 ...
给Extjs的GridPanel增加“合计”行 -
rhhao:
这个附件怎么用呢?
自己写段代码批量修改照片的Exif数据 -
AndLong:
【转】关于烂代码的那些事(下) -
TonyLian:
无意中翻出这篇老博文,文章中留下的遗憾“纯JSP如何获取req ...
Spring获得各种客户端HttpServletRequest的方法 -
TonyLian:
注释中应该写“这里为什么要做XXX”,“为什么这里没有做XXX ...
【转】关于烂代码的那些事(中)
原作者在这一条上用了8页的篇幅,翻译版也有7页,足以说明这一条的重要性。我个人对此条的标注是重量级的5颗星!
克隆——是一个很让人“感兴趣”而又“颇有争议”的话题,无论是在生物界还是在代码的世界中。
Java通过实现Cloneable接口来“说明”一个类支持clone方法。所谓clone就是返回一个当前对象的副本,注意这里所返回的是一个复制品,虽然它的内容应该与原对象完全一致(这才叫克隆吗),但是它们的地址指针却是不同的,区别于简单的地址赋值(= 操作符)。
Object中clone方法的定义是:
protected native Object clone() throws CloneNotSupportedException;
首先它是保护的,其次它是native(本地)的,也就是说它是通过其他语言编写的代码,是看不到源码的,最后它可能抛出CloneNotSupportedException,在类不支持克隆时。
Object.clone() 采用了本地方法,通过其他语言予以实现,可能是为了提高性能,也可能是其他什么原因,我们就不深究了。我们还是看看Cloneable接口吧:
public interface Cloneable { }
它其实是个空空的,具体方法一个也没有。那么它到底做了什么呢?它决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,则Object的clone方法返回该对象的逐域拷贝,否则的话抛出一个CloneNotSupportedException异常。这是接口的一种极端非典型的用法,也不值得效仿。
那么既然实现了Cloneable接口后,就可以调用Object中的clone方法了(Cloneable接口改变了超类中一个受保护的方法的行为),那我们的目的不就达到了吗?干吗还要改写clone呢?
Object的clone方法,只能逐域拷贝那些原语类型,对于类仅仅是地址赋值,换句话说,它只是逐域在做 = 操作。这样并不是完全的克隆,所以我们需要改写clone方法。
Object中关于克隆的约定:
1) x.clone() != x ,将会为 true
2) x.clone().getClass() == x.getClass() ,将会为 true
3) x.clone().equals(x) , 将会为 true
“将会为true”,但是这也不是一个绝对要求。拷贝往往会导致创建一个新实例,但同时也会要求拷贝内部的数据结构。这个过程中没有调用构造函数。
“没有调用构造函数”和“x.clone().getClass() == x.getClass() ” 的综合导致结果就是:如果你改写一个非final类的clone方法,则应该返回一个通过调用super.clone而得到的对象(具体推到过程见书上的第40页)。这其实也相当于给我提供了一个改写clone方法的“处方”:
不要使用构造函数来创建类,而是使用超类的clone方法。
于是clone方法的改写模板可以是这样的:
public class MyClass{ ........ public Object clone() { MyClass v = null; try{ v = (MyClass) super.clone(); // 逐域克隆MyClass的非原生类型域 } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } return v; } }
简而言之,所有实现了Cloneable接口的类都应该用一个公有的方法改写clone。此方法首先调用super.clone,然后修正任何需要修正的域。通常情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并且要用指向新对象的引用代替原来指向这些对象的引用。虽然,这些内部拷贝操作往往可以通过递归地调用clone来完成,但这通常并不是最佳方法。通常原语类型和非可变对象时无需修改的,但也有例外情况,譬如,一些代表唯一ID的序列号或代表对象创建时间的域,虽然是原语类型或非可变对象,也要被修改。
其实,只里面的一句“然后修正任何需要修正的域”,可非同小可。“这意味着要拷贝任何包含内部‘深层结构’的可变对象”,这可就复杂了。如果域是一个数组的话,需要新创建一个相同大小的数组,并对每一个数组元素递归地调用他们的clone方法。
但是,不幸的事情发生了。我在实践clone的时候发现,Java的集合类型其实实现的只是“浅表克隆”,也就是说它们并没有“逐域递归克隆深层结构”。为什么呢?其实,如果你自己去实现一个数组的“深层克隆”就会发现,由于放入数组(集合)中的元素可能是各种类型,所以只能是Object,而Object本身是无法clone的。理由很简单,不是所有的类都实现了Cloneable接口,那么如果一个放入数组(集合)中的对象是一个没有实现Cloneable接口的类,那么就无从谈起对它的克隆了。所以,约定中说的是“这意味着要拷贝任何包含内部‘深层结构’的可变对象”而不是“这意味着要递归克隆任何包含内部‘深层结构’的可变对象”。注意用的词是“拷贝”而不是“克隆”,因为任何类型都可以“拷贝”,其方法就不仅限于“克隆”了。
那么该如何进行一个没有实现Cloneable接口的对象的“拷贝”工作呢?我们来举一个 BigDecimal 的例子。在调用完超类的clone方法之后,就该修改这些需要被修改的域了。
v.varBigDecimal = new BigDecimal(this.varBigDecimal.toString());
由于BigDecimal既没有实参类型为BigDecimal的valueOf静态工厂方法,也没有实参类型为BigDecimal的构造函数,所以只能通过String型来做“传递”。
那么对于域中包含数组、集合,而声明时又没有指定其元素类型的类(尤其是那些用来被继承的超类本来就无从知晓类型)来说,这里的“修改”方法将变得异常复杂,你必须去判断每个元素的类型,当然还要求尽量考虑到所有可能的类型,但这也不能保证日后使用过程中不会出现之前未知的类型。
再加之,各集合类型的clone方法已经是写好的了,而且很不幸,它们并没有如上方法去处理,而只是对内部元素的引用赋值,如果你想实现一个严格的clone方法,而你的类当中又可能出现集合类型的域,那么很不幸,你只有逐一补足那么集合类型的clone方法了(在调用原有的clone方法后,逐一遍历其内部元素并克隆之,里面还有集合怎么办?$#%&$#% 晕倒~ 崩溃~)
看到这,也许你对clone的看法也会和我一样,真的要这么复杂吗?是的,所以书中这一条的题目才是“谨慎地”改写clone。同时,书中也给我们指了出路——最好的方法是,提供某些其他途径来代替对象拷贝,或干脆不提供这样的能力。一个好的代替方法是“拷贝构造函数”
public MyClass(MyClass myClass);
或另一种微小变形——静态工厂方法
public static MyClass newInstance(MyClass myClass);
下面再说一点本书以外的内容,首先是关于“深度”、“浅表”和“影子”拷贝。
“影子”一词是我自己“创造”的,其实和其他人所谓的“浅表”大致是一个意思。而我所谓的“浅表”,不仅仅是没有完成对深层对象的拷贝,而且甚至连引用赋值也没有做的才叫“浅表”,而“影子”拷贝是进行了引用赋值的。那么“浅表”拷贝的意义在与何呢?由于它对那些类对象什么也没有做,还保留着“原来”的样子,所以它的用意是“保护”那些非原语类型的域。在clone中,由于副本对象是刚刚通过super.clone()创建的,所以那些非原语类型的域实际上应该已经完成了引用的赋值,所以“保护”变成极其有限度的“加速”。它的“保护”作用更重要的体现地是copyProperties。
第二是,克隆的应用场景。
正如很多人不喜欢克隆,或者说clone有着很多争议那样,“如果你的代码中经常要使用clone,那么可以错略地说,你的设计是有问题的”。那么什么时候使用clone呢?一个典型的案例是:当从服务端得到查询结果后,将结果显示到GUI之上的同时,将其clone一份并保存起来。当用户对GUI上的信息进行了编辑之后按下“保存”按钮想要保存时,这时调出来刚才保存的clone档对比一下,如果发现并没有区别(x.equals(y) == true),将提示用户没有可保存的修改。可能是用户改来改去后,结果又回到了当初,这种情况是较为常见的。
而在服务端,创建一个对象的副本,然后将原对象送入某一方法,在这个方法执行过后,再去比较原对象和副本对象是否还equals,类似这样的设计显然是不好的。
最后再来说一下实践中比较实用的clone的替代品:
1) 如果一个类是可序列化的(实现系列化),那么可以将其先序列化,然后再反序列化已得到其副本。如果对象很大,甚至可以序列化到磁盘上。
2) 更普遍的,可以将一个对象转为字符流,然后在进而转到副本对象中。不依赖于类必须可系列化,所以更通用。
3) 如果是JavaBean(就像我的情况),可以使用org.apache.commons.beanutils.BeanUtils.cloneBean静态方法
【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208
发表评论
-
【第48条】对共享可变数据的同步访问
2009-06-23 12:18 1440《第9章 线 程》 通过使用线程(thread ... -
【第47条】不要忽略异常
2009-06-15 11:51 1414作为本章的最后一条,此条目是一条“纪律”,一条你必须遵 ... -
【第46条】努力使失败保持原子性
2009-06-15 11:23 1642所谓失败的原子性,就是在一个方法失败之后,使对象保持“它 ... -
【第45条】在细节消息中包含失败-捕获信息
2009-06-15 10:54 1376这一条是写给那些自己写Exception的程序员的。 ... -
【第44条】每个方法抛出的异常都要有文档
2009-06-09 17:12 1603虽然在【第40条】中说到了,Java的throws语法 ... -
【第43条】抛出的异常要合适于相应的抽象
2009-06-09 16:52 1504如果一个方法抛出的异常与它所执行的任务没有明显关联关系 ... -
【第42条】尽量使用标准的异常
2009-06-09 16:37 1394代码重用,是程序员们“千百年来”所追求的目标,同样Ex ... -
【第41条】避免不必要地使用被检查的异常
2009-06-09 16:21 1462也就是说,在使用checkedException时,你必 ... -
【第40条】对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常
2009-06-09 12:09 2050Java一共有三种可 ... -
【第39条】只针对不正常的条件才使用异常
2009-06-09 11:28 1335《第8章 异常》 异常是Java语言中非常重要 ... -
【第38条】遵守普遍接受的命名惯例
2009-06-03 17:15 1889Java平台(其实整 ... -
【第37条】谨慎地进行优化
2009-06-03 15:57 1492这一条我没有什么好评价的,记住作者的忠告就好。 ... -
【第36条】谨慎地使用本地方法
2009-06-03 15:50 1505Java Native Interface(JNI,J ... -
【第35条】接口优于映像机制
2009-05-31 15:18 1208我个人更觉得,“映像机制”翻译为“反射机制”应该更好一 ... -
【第34条】通过接口引用对象
2009-05-31 15:04 2254在【第25条】中已经讲过“应该使用接口,而不是类作为参数的类型 ... -
【第33条】了解字符串连接的性能
2009-05-27 15:58 1362这一条是一个良好的“习惯”,你可能一直没有注意到,但也 ... -
【第32条】如果其他类型更合适,请尽量避免使用字符串
2009-05-27 15:40 1574这两天在研究通过Hessian远程连接Java和C#。功能强大 ... -
【第29条】将局部变量的作用域最小化
2009-05-25 17:33 2054《第7章 通用程序设计》 本章主要讨论Java语言的语 ... -
【第31条】如果要求精确的答案,请避免使用float和double
2009-05-25 16:30 2409float和double型,的底层实现是二进制的。十进制 ... -
【第30条】了解和使用库
2009-05-25 15:46 1285Java语言的丰富多彩,很大程度上是体现在丰富的类库上 ...
相关推荐
标签“KYO CLONE 10”可能是这个工具的特定品牌或者开发者,"KYO"可能是一个公司或者开发团队的名称,他们专注于克隆技术,并且已经发展到了第10代产品。 由于提供的压缩文件名为“Clone10-EX-LV2”,我们可以推测...
Java中的`clone`方法是Java语言提供的一种复制对象的方式,它允许创建一个对象的副本,这个副本与原对象具有相同的属性值,但它们是两个独立的对象,修改副本不会影响原对象。`clone`方法存在于Java的`java.lang....
至于文件列表中的"lib",这通常表示库文件或者依赖项,可能包含了一些用于演示`clone()`方法使用的第三方库或者Java标准库。在实际项目中,这些库文件可以帮助开发者完成更多的功能,例如日志记录、单元测试等。 ...
### Java中的`clone`方法详解:浅拷贝与深拷贝 #### 一、引言 在Java中,`clone`方法提供了一种快速复制对象的方式。它属于`Object`类的一部分,但需要显式地在子类中声明并实现`Cloneable`接口才能正常使用。本文...
理解其工作原理,以及何时和如何正确地使用`clone`,对于提升代码质量和效率至关重要。在实践中,根据具体需求选择合适的复制策略,如浅拷贝、深拷贝,或者使用其他构造方式,是提高代码健壮性的关键。
首先,`clone()`方法是Java Object类的一个成员方法,这意味着所有Java类都隐式地继承了这个方法。它的主要作用是创建并返回当前对象的一个副本,也就是浅拷贝。浅拷贝意味着新创建的对象拥有原始对象的属性值,但...
### Git Clone与Submodule知识点详解 #### 一、Git Clone命令 `git clone` 命令用于克隆一个远程仓库到本地。当你需要获取一个项目的全部文件时,这个命令非常有用。 **基本用法:** ```bash git clone ``` **...
- 重写`clone()`方法时,需要处理非基本类型的成员变量,确保它们也能够被正确地复制。 总的来说,Java中的克隆机制提供了复制对象的能力,这在很多场景下都非常有用,比如数据备份、并发操作、状态恢复等。但需要...
"git clone"是Git中的一个核心命令,用于复制远程仓库到本地。在本文中,我们将深入探讨`git clone`命令以及如何获取其最新版本。 首先,让我们了解`git clone`的基本用法。当你运行`git clone <repository>`时,它...
在IT行业中,Jlink通常指的是SEGGER公司的J-Link系列编程和调试接口,它广泛用于嵌入式系统开发,特别是针对ARM架构的设备。当遇到"Jlink-clone...在尝试此类解决方案时,保持谨慎并做好备份,以避免不必要的损失。
`jlink v9 warning clone`问题通常涉及到JLink版本9在与MDK配合使用时遇到的警告,提示可能与克隆设备或非法设备相关。 标题中的"jlink v9 warning clone解决"意味着开发者正在尝试解决关于JLink v9版本出现的克隆...
在Windows环境中,Git批量操作是开发团队...通过熟练掌握以上知识点,你就能创建出高效且可维护的批处理脚本来管理多个Git仓库,极大地提高工作效率。记得根据实际需求对这些脚本进行个性化调整,以适应你的工作流程。
Minesweeper CLone 0.97 Saolei.net扫雷网,小门汉化 Men Shiyun Mine.exe 请自行下载MinesweeperClone_0.97.exe安装后使用
jquery.clone
详细的描述了Java中 clone方法使用
在使用高版本版KEIL时,提示要升级固件,升级后就出现JLINK is Clone的提示!“the emulator is JLink-Clone, the segger software only support orginal segger device” 然后闪退,IDE崩溃关闭! 解决方案: 1....
"clone-voice.zip" 是一个压缩包文件,很可能包含了与语音克隆技术相关的代码或工具。根据提供的标签 "python",我们可以推测这个项目是使用Python编程语言实现的。Python在处理音频数据和人工智能领域有着广泛的...
在Java编程语言中,`Cloneable`接口和`clone()`方法是两个重要的概念,它们用于对象复制。在本文中,我们将深入探讨Java中的浅克隆(shallow clone)和深克隆(deep clone),并结合测试代码进行分析。 首先,让...
用户可能只需要提供少量的个人语音样本,模型就能调整参数,以尽可能接近地模仿该人的发音和语调。 在"voice clone.zip"中,预训练模型可能已经过优化,可以处理中文语音的特殊性,如声调变化对意义的影响。使用者...
matlab开发-MinesweeperClone。用于Matlab的Microsoft Windows XP扫雷器克隆