我们知道,在java的object类中,有这么一个方法clone(),这个方法有什么用呢?怎样才能正确地使用这个方法呢?
下面一一来进行阐述一下
顾名思义,clone()方法的作用就是克隆的意思,引入这个方法,这样就便于我们构建属于自己的一些本地对象副本。
这样我们就不用担心因为副本对象的引用而使原生的对象发生改变。这就是克隆所能带给我们的好处。
默认情况下,类是不具备克隆的能力的,需要再额外添加一些代码使其具备克隆能力。
I. 实现Cloneable 接口
II. 覆盖clone()
III. 在自己的clone()中调用super.clone()
IV. 在自己的clone()中捕获异常
参考示例如下:
package com.example.example_01; /** * * 版权:XXX公司 版权所有 * * 作者:will smith * * 版本:1.0 * * 创建日期:2016/10/23 * * 描述: * * 修订历史: * */ //实现Cloneable接口 public class CloneSample implements Cloneable{ private int i; /** * 代码测试部分 * @param args */ public static void main(String[] args){ CloneSample sample = new CloneSample(10); System.out.println("i: " + sample.i); CloneSample sampleCopy = (CloneSample) sample.clone(); sampleCopy.i++; System.out.println("copy i: " + sampleCopy.i); System.out.println("i: " + sample.i); } public CloneSample(int i) { this.i = i; } //重写clone @Override public Object clone(){ Object o = null; try { o = super.clone(); //调用父类的clone } catch (CloneNotSupportedException e) { //异常捕获 e.printStackTrace(); } return o; } }
运行结果如下:
代码分析:
由于CloneSample重写了clone()方法并实现了Cloneable接口,所以当调用CloneSample对象的clone()方法时,
就会克隆出一份CloneSample对象的副本,在克隆时,属性和方法都对照着拷贝,由于在拷贝属性时,其属性的
类型为 int ,所以当然是值传递,所以拷贝之后的副本和之前的副本就没有任何联系了。所以在对副本的属性 i 进行
++时,自然就不会影响原对象的属性 i 的值了。
若原对象的属性中含有引用类型,则其拷贝的就是句柄,这样会导致原对象属性的引用和副本对象属性的引用都指向
同一个对象,这样当副本对象修改引用类型属性的某个属性值时,就间接会影响原对象的引用类型属性的属性值,那么
如何才能真正达到两者完全分离呢?你看第三部分:深拷贝和浅拷贝。
我们再来看一组调用clone()类的方法
package com.example.example_02; /** * * 版权:XXX公司 版权所有 * * 作者:will smith * * 版本:1.0 * * 创建日期:2016/10/23 * * 描述:用来比较深拷贝和浅拷贝 * * 修订历史: * */ public class CloneSampleDiff implements Cloneable{ Shallow shallow; Deep deep; public CloneSampleDiff() { shallow = new Shallow(); deep = new Deep(); } public static void main(String[] args){ CloneSampleDiff sampleDiff = new CloneSampleDiff(); CloneSampleDiff sampleDiffCopy = (CloneSampleDiff) sampleDiff.clone(); /** * 改变之前的输出 */ System.out.println("sampleDiff.shallow.tempShallow: " + sampleDiff.shallow.tempShallow); System.out.println("sampleDiff.deep.tempDeep: " + sampleDiff.deep.tempDeep); System.out.println("sampleDiffCopy.shallow.tempShallow: " + sampleDiffCopy.shallow.tempShallow); System.out.println("sampleDiffCopy.deep.tempDeep: " + sampleDiffCopy.deep.tempDeep); /*** * 改变tempClass和tempClone属性 */ sampleDiffCopy.shallow.tempShallow++; sampleDiffCopy.deep.tempDeep++; System.out.println("===================================="); /** * 改变之后的输出 */ System.out.println("sampleDiff.shallow.tempShallow: " + sampleDiff.shallow.tempShallow); System.out.println("sampleDiff.deep.tempDeep: " + sampleDiff.deep.tempDeep); System.out.println("sampleDiffCopy.shallow.tempShallow: " + sampleDiffCopy.shallow.tempShallow); System.out.println("sampleDiffCopy.deep.tempDeep: " + sampleDiffCopy.deep.tempDeep); } @Override public Object clone() { CloneSampleDiff o = null; try { o = (CloneSampleDiff) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } /** * 注意观察这里 */ o.deep = (Deep) o.deep.clone(); return o; } } /** * 实现了Cloneable */ class Shallow implements Cloneable{ int tempShallow; public Shallow() { tempShallow = 10; } @Override public Object clone() { Object o = new Object(); try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } } /** * 实现了Cloneable */ class Deep implements Cloneable{ int tempDeep; public Deep() { tempDeep = 20; } @Override public Object clone(){ Object o = new Object(); try { o = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; } }
输出结果:
代码分析:
在分析之前,我们先来大概看一下上述这个类:
其包含了两个属性,分别为:shallow和deep,而shallow和deep都分别具有了克隆的功能,
两者唯一的区别就是在CloneSampleDiff类的clone()方法中,对于deep属性,增加了一条语句:
o.deep = (Deep) o.deep.clone();
也就是这条语句造就了两者中的tempShallow和tempDeep属性值的变化有所差异,
也就是当对副本sampleDiff执行
sampleDiffCopy.shallow.tempShallow++;
其原来的对象sampleDiff.shallow.tempShallow也会一同变化,这就是我们所说的浅拷贝。
而当副本sampleDiff执行
sampleDiffCopy.deep.tempDeep++;
其原来的对象sampleDiffCopy.deep.tempDeep就没有发生变化,这也就真正达到了我们
拷贝的目的,这就是我们所说的深拷贝。
深拷贝和浅拷贝出现的原因在于:
拷贝时,若属性为主类型,则由于其是值传递,所以自然不会有影响。
若属性为对象,我们知道,其实质是句柄,那么在拷贝时,自然也就
拷贝句柄,这样拷贝的结果是:两个相同属性的句柄指向了一个对象。
自然,当我们用一个句柄去改变对象的值时,自然另一个句柄的所
指向的值也就发生变化。
为了真正达到深拷贝,需要满足两点:
第一:这个属性是具有克隆功能的
第二:在clone()方法中,再去调用对应属性的clone()方法予以赋值,如本示例中的deep属性:
@Override public Object clone() { CloneSampleDiff o = null; try { o = (CloneSampleDiff) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } /** * 注意观察这里 */ o.deep = (Deep) o.deep.clone(); return o; }
在第三部分中,我们知道,一个属性若要实现深拷贝,则需要让这个属性具有克隆的功能,
而要让这个属性具有克隆的功能,我们从第二部分可以知道:需要实现Cloneable接口及重写clone()方法。
但倘若一个类是继承一个已具有克隆功能的类的时候,此时,我们是否还有必要再去实现克隆功能呢?
下面,我们来看一下下面这段代码:
package com.example.example_03; /** * * 版权:XXX公司 版权所有 * * 作者:will smith * * 版本:1.0 * * 创建日期:2016/10/23 * * 描述:参考Java编程思想示例 * * 修订历史: * */ class Person {} class Hero extends Person {} class Scientist extends Person implements Cloneable { public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // this should never happen: // It's Cloneable already! throw new InternalError(); } } } class MadScientist extends Scientist {} public class HorrorFlick { public static void main(String[] args) { Person p = new Person(); Hero h = new Hero(); Scientist s = new Scientist(); MadScientist m = new MadScientist(); // p = (Person)p.clone(); // Compile error // h = (Hero)h.clone(); // Compile error s = (Scientist)s.clone(); m = (MadScientist)m.clone(); } }
代码分析:
若新建一个类,它的基础类会默认为Object,并默认为不具备克隆能力。只要不明确地添加克隆能力,这种能力便不会自动产生。
但我们可以在任何层添加它,然后便可从那个层开始向下具有克隆能力。
我们知道,其实序列化从某种意义上也可是实现克隆的一种方式,那有了序列化,为何还要克隆呢?
因为克隆性能更高,所需消耗的时间更短。
clone()方法详解示例源码
Java编程思想
关注微信公众号获取更多资讯