java的clone()的使用
clone()方法的约定
首先明确的是clone()是object的方法。Cloneable接口没有任何方法,它只起到标识的作用。(java的原型模式有用到)
Cloneable接口的目的是作为对象的一个混合接口,表明这样的对象允许克隆(clone),但是这个接口却没有定义clone(),这是它的缺陷:无法约束子类实现clone()方法。
Object定义了一个受保护的clone()方法。Cloneable虽然没有定义clone()方法,但是却影响了Object.clone()方法的行为:如果一个类实现了Cloneable,调用Object的clone()就会返回该对象的逐域拷贝,否则抛出CloneNotSupportedException。这真是一种非常规的用法,Cloneable接口没有规定实现类的视图,却改变了父类受保护方法的行为。调用clone()会创建并返回对象的拷贝,看看JDK文档中对clone()方法的约定:
(1)x.clone() != x; 克隆对象与原对象不是同一个对象
(2)x.clone().getClass() == x.getClass(); 克隆的是同一类型的对象
(3)x.clone().equals(x) == true,如果x.equals()方法定义恰当的话
注意,上面的三条规则要求不是绝对的,一般来说前两条是必需的,第三个也应该尽量遵守。
实现Cloneable接口的类和其所有超类都必需遵守一个复杂、不可实施、且没有文档说明的协议,由此得到一种语言之外的机制:无需调用构造器就可以创建对象。然而,“不调用构造器”的规定有些僵硬,行为良好的clone()方法可以调用构造器创建对象,比如final类,它不会有子类,所以在它的clone()方法中调用构造器创建对象是一种合理的选择。
使用clone()的规则
“如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone()而得到的对象”,这是使用clone()方法的规则,如果不遵守这条规则,在clone()方法中调用了构造器,那么就会得到错误的类。如代码所示:
- class A implements Cloneable
- {<p style="margin:0in;font-size:10.5pt"><span style="font-family:Calibri" lang="en-US"> //</span><span style="font-family:SimSun" lang="zh-CN">类</span><span style="font-family:Calibri" lang="en-US">A</span><span style="font-family:SimSun" lang="zh-CN">的</span><span style="font-family:Calibri" lang="en-US">clone()</span><span style="font-family:SimSun" lang="zh-CN">直接调用构造器</span></p> public A clone() {
- return new A();
- }
- }
- class B extends A
- {
- public B clone() {
- try
- {
- return (B) super.clone();
- }
- catch (CloneNotSupportedException e)
- {
- throw new AssertionError();
- }
- }
- }
类B的clone()方法就不会得到正确的对象,因为super.clone()返回的是使用A的构造器创建的类A的对象。如果类B的clone方法想得到正确的对象,那么A的clone方法应该这样写:
- public A clone() {
- try{
- return (A) super.clone();
- }catch (CloneNotSupportedException e){
- throw new AssertionError();
- }
- }
由此,我们可以看出调用super.clone()最终会调用Object类的clone方法,前提是子类的所有超类都遵循了上面的规则,否则无法实施。注意,A和B的clone方法的返回值不必是Object,Java1.5引入了协变返回类型作为泛型,覆盖方法的返回值可以是被覆盖方法返回值的子类。
Cloneable实在是一个失败的接口,它并没有指明实现它的类需要承担哪些责任,通常情况下,实现Cloneable的类应当提供一个功能适当的公有的clone()方法。
浅克隆
克隆出来的对象的所有变量含有与原来的对象相同的值,而对其他对象的引用都指向原来的对象。也就是说,浅克隆仅仅克隆所考虑的对象。Object的clone就是"shallow copy"。如果类的每个域都是基本类型的值,或者是指向不可变对象的引用,那么调用Object.clone()就能得到正确的对象。
- /**
- *如果每个域都是基本类型,或者指向不可变对象的引用
- *那么这个类只需要声明实现Cloneable接口,提供公有的clone()方法
- */
- class ShallowCopy implements Cloneable
- {
- private String name;
- private int no;
- public ShallowCopy(String name,int no) {
- this.name = name;
- this.no = no;
- }
- /*只需调用super.clone()就能得到正确的行为*/
- public ShallowCopy clone() {
- try
- {
- return (ShallowCopy)super.clone();
- }
- catch (CloneNotSupportedException e)
- {
- throw new AssertionError();
- }
- }
- }
通常情况下,我们已经得到了正确的对象,但是如果类里面包含代表序列号或者唯一ID的域,或者创建时间的域,还需要对这些域进行修正。
深克隆
深克隆把引用域所指向的对象也克隆一遍。考虑下面这样一个类:
- class Person
- {
- private Dog friend;
- public Person(Dog dog) {
- friend = dog;
- }
- }
- class Dog
- {
- private String name;
- public Dog(String name) {
- this.name = name;
- }
- }
Person类的friend域不是基本类型,而是指向了可变的对象,这个时候如果调用Object.clone()进行浅克隆,那么克隆出来的对象的friend指向的还是原来的dog,就是说:
Person p = new Person(new Dog("金毛"));
p.clone().friend== p.friend;//true
p.clone().friend.name = "狼狗";
p.friend.name.equals("狼狗");//true,改变克隆对象,却同时更改了原对象
实际上,clone方法是另一种构造器:你必须确保不会伤害到原来的对象。为了使Person的clone方法正确工作,也要对friend进行克隆,最简单的做法就是调用friend.clone():
- public Person clone() {
- try{
- Person result = (Person) super.clone();
- result.friend = friend.clone();
- }
- catch (CloneNotSupportedException e){
- throw new AssertionError();
- }
- }
可是,如果friend域是final的,那么上面的clone()也无法正常工作,因为super.clone()时已经给friend赋一次值了,不能再去修正克隆对象的friend域了。这是个根本问题:clone架构与引用可变对象的final域的正常用法是不相兼容的!
抛去final域的问题不谈,递归的调用clone()方法就解决问题了吗?问题在于,深克隆要深入到哪一层,是一个不易确定的问题。考虑下面的类:
- import java.util.Arrays;
- /**
- *内部实现了单向链表
- *buckets里的每个元素保存一个单向链表
- *
- */
- class NMap implements Cloneable
- {
- private Entry[] buckets;
- public NMap(int size) {
- buckets = new Entry[size];
- for(int i = 0; i < size; i++)
- buckets[i] = new Entry("M10","Messi",new Entry("X6", "Xavi", null));
- }
- public Entry[] getBuckets() {
- return buckets;
- }
- static class Entry
- {
- final Object key;
- Object value;
- Entry next;
- Entry(Object key,Object value,Entry next) {
- this.key = key;
- this.value = value;
- this.next = next;
- }
- public void setNext(Entry next) {
- this.next = next;
- }
- public String toString() {
- String result = key + ":" + value + " ";
- if(next != null)
- result += next.toString();
- return result;
- }
- }
- public NMap clone(){
- try
- {
- NMap result = (NMap)super.clone();
- //数组被视为实现了Cloneable接口
- result.buckets = buckets.clone();
- return result;
- }catch (CloneNotSupportedException e){
- throw new AssertionError();
- }
- }
- public static void main(String[] args) {
- NMap map = new NMap(5);
- System.out.println(Arrays.toString(map.getBuckets()));
- NMap clone = map.clone();
- Entry entry = new Entry("G4","Guadiorla",new Entry("R9","Ronaldo",null));
- for(Entry ent : clone.getBuckets())
- ent.setNext(entry);
- System.out.println(Arrays.toString(map.getBuckets()));
- }
- }
运行这段代码之后会发现,虽然克隆对象有自己的数组buckets,但是数组中引用的链表与原始对象是一样的,修改克隆对象数组中的链表,原始对象中数组保存的对象也会随之而修改。解决这种问题的方法是在Entry类中增加一个“深度拷贝(deep copy)”方法。
- public Entry deepEntry() {
- Entry result = new Entry(key,value,next);
- for(Entry p = result; p.next != null; p = p.next)
- p.next = new Entry(p.next.key,p.next.value,p.next.next);
- return result;
- }
- NMap的clone()方法如下:
- public NMap clone(){
- try
- {
- NMap result = (NMap)super.clone();
- //数组被视为实现了Cloneable接口
- result.buckets = buckets.clone();
- for(int i = 0; i < buckets.length; i++)
- if(buckets[i] != null)
- result.buckets[i] = buckets[i].deepEntry();
- return result;
- }catch (CloneNotSupportedException e){
- throw new AssertionError();
- }
- }
克隆复杂对象还有一种方法,先调用super.clone()得到类型正确的对象,然后把所有域都设置成空白状态,然后调用高层的方法重新产生对象的状态。这种做法会产生一个简单、合理且相当优美的clone方法,运行速度稍慢。
总结
1.Cloneable接口是一个失败的接口,它没有提供clone()方法,却影响了Object.clone()克隆的行为:如果类没有实现Cloneable接口,调用super.clone()方法会得到CloneNotSupportedException。
2.所有实现了Cloneable接口的类都应该提供一个公有的方法覆盖clone(),此公有方法首先调用super.clone(),然后修正域,此公有方法一般不应该声明抛出CloneNotSupportedException。
3.如果为了继承而设计的类不应该实现Cloneable接口,这样可以使子类具有实现或者不实现Cloneable接口的自由,就仿佛它们直接扩展了Object一样。父类没有实现Cloneable接口,也没有覆盖clone(),子类如果实现了Cloneable,在覆盖的clone()中调用super.clone()是可以得到正确对象的。
据说很多专家级程序猿从来都不使用clone()方法。
更好的方法
等等,为了实现clone()方法的功能,有必要这么复杂吗?很少有这种必要。为了实现对象拷贝的更好的方法是提供一个拷贝构造器或者拷贝工厂,它们接受这类的一个对象作为参数。
public Yum( Yum yum);//拷贝构造器
public static YumcopyInstance(Yum yum);//拷贝工厂
转载请注明出处:喻红叶《Java中的clone()方法》
java的clone()的使用的更多相关文章
- Java基础——clone()方法浅析
一.clone的概念 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...
- 分析java中clone()方法 (转载+修改)
Java中的clone() 方法 java所有的类都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制. protected native Object c ...
- java中clone的深入理解
Java中Clone的概念大家应该都很熟悉了,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Clone机制是如何工作的? 1. Clone和Copy 假 ...
- Java中clone方法的使用
什么是clone 在实际编程过程中,我们常常要遇到这种情况:有一个对象object1,在某一时刻object1中已经包含了一些有效值,此时可能会需要一个和object1完全相同新对象object2,并 ...
- Java Object Clone
Java Object Clone User user = new User(); user.setName("tom"); User user1 = new User(); us ...
- Java的clone方法效率问题
在Java中,经常会需要新建一个对象,很多情况下,需要这个新建的对象和现有的某个对象保持属性一致. 那么,就有两种方式来实现这个对象的构造: ①通过新建一个对象,为这个对象的属性根据原有对象的属性来进 ...
- 关于Java深clone 的例子学习
之前http://www.cnblogs.com/lhppom/p/4857702.html里有提到关于Java的深克隆的学习,深浅区别就是在于仅复制对象引用和复制对象引用所指向的对象,最近在看< ...
- java的 clone方法
1.java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如果取操作这个指针而已. 2.在java中处理基本数据类型时,都是 ...
- java的clone
做项目时有时可能会遇到需要克隆对象的时候,因为有时候对象是直接从别的类get到的,那样引用的是一个对象,修改的话会将原先的对象也修改了. java的浅克隆,十分简单.但是只会克隆基本的数据类型,当涉及 ...
- java的clone()方法
什么是"clone"? 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都 ...
随机推荐
- 百思不得姐之"我的"模块功能(六)
一 功能图和知识点 1 功能图部分:(因为网速的原因,网页部分没有载入出来,可是功能完善) 2 该部分能学到的知识点概括: >1 UITableView的使用(简单) >2 UIColle ...
- MySQL 事务1
本人应用的MySQL的版本为:5.6.22
- 【BZOJ4237】稻草人 cdq分治+单调栈+二分
[BZOJ4237]稻草人 Description JOI村有一片荒地,上面竖着N个稻草人,村民们每年多次在稻草人们的周围举行祭典. 有一次,JOI村的村长听到了稻草人们的启示,计划在荒地中开垦一片田 ...
- Windows操作系统远程Linux服务器传输文件方法(以EasyDSS云平台、EasyNVR上传部署为例)
本文转自博客:https://blog.csdn.net/black_3717/article/details/79769406 问题背景: 之前给客户部署我们一款EasyDSS云平台(配合EasyN ...
- EasyPlayerPro windows播放器本地音频播放音量控制实现
背景描述 作为一个播放器, 除了能播放视频和声音外,音量控制是绝对不能缺少的功能; 本文在音视频播放的基础上,增加对音量的控制: 实现流程 调用mixerGetDevCaps获取音频输出设备列表; 打 ...
- django框架小技巧
带命名空间的URL名字 多应用中路由定义,采用命名空间,防止冲突 url(r'^polls/', include('polls.urls', namespace="polls")) ...
- Centos7重新安装yum
Centos7重新安装yum rpm -qa|grep yum 然后用下面的命令删除出现的xxx包: rpm -e --nodeps xxx 下载 python-urlgrabber-3.10-8.e ...
- 关于EF输出sql的执行日志
sqlserver中可以使用sql profiler:但是mysql当中无法查看:只能借助于组件: ADO.NET Entity Framework CodeFirst 如何输出日志(EF4.3) 用 ...
- 九度OJ 1152:点菜问题 (01背包、DP)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1046 解决:543 题目描述: 北大网络实验室经常有活动需要叫外买,但是每次叫外买的报销经费的总额最大为C元,有N种菜可以点,经过长时间的 ...
- new() 和 make() 的区别
https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md