(干货)java clone()详解

什么是"clone"?

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B 任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需 求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

java的clone()分为深复制与浅复制

对于Java的clone方法,需要注意的就是它实际上是一种“浅克隆”(Shallow Clone),对于int、double这种基本数据类型,直接拷贝值;对于String这样的类对象,则是直接拷贝引用。因此对于通过clone得到的对象来说,它很大程度上还是和原有被克隆对象之间有着很大的联系(例如修改clone对象的String属性,则原有对象的String属性也会变化)。
new对象与浅克隆效率对比

  • ①对于轻量型的类对象,通过new操作来构造,效率会更高。
  • ②对于重量型的类对象,通过clone操作来进行构造,效率会更高。
    原因分析:
  • ①对于轻量型的类对象,通过new操作即可很快地进行构造,而clone方法依旧涉及到new操作,但其会进行更多的方法调用,执行流程会消耗一些时间
  • ②对于重量型的类对象,new操作则需要构造相当多的对象,从而会消耗很多时间;但clone方法(这里是“浅克隆”)只需要拷贝引用给新的对象即可,因此消耗的时间会更少

Java中要想自定义类的对象可以被复制,自定义类就必须实现Cloneable中的clone()方法

@Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

怎么进行深度clone?

public class DemoClass implements AccountDao,Cloneable {
	@Override
    protected Object clone() throws CloneNotSupportedException {
        DemoClass o=(DemoClass)super.clone();
        return o;
    }
}

要知道不是所有的类都能实现深度clone的。StringBuffer,看一下 JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个 final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和 StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是 SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();

还要知道的是除了基本数据类型能自动实现深度clone以外,String对象,Integer,Double等是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

真的是深拷贝吗?

由上一节的内容可以得出如下结论:如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
那么,按照上面的结论, Body类组合了Head类, 而Head类组合了Face类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份,但是在拷贝Head类时,默认执行的是浅拷贝,也就是说Head中组合的Face对象并不会被拷贝。验证代码如下:

static class Body implements Cloneable{  
    public Head head;  
    public Body() {}  
    public Body(Head head) {this.head = head;}  
 
    @Override 
    protected Object clone() throws CloneNotSupportedException {  
        Body newBody =  (Body) super.clone();  
        newBody.head = (Head) head.clone();  
        return newBody;  
    }  
 
}  
 
static class Head implements Cloneable{  
    public  Face face;  
 
    public Head() {}  
    public Head(Face face){this.face = face;}  
    @Override 
    protected Object clone() throws CloneNotSupportedException {  
        return super.clone();  
    }  
}   
 
static class Face{}  
 
public static void main(String[] args) throws CloneNotSupportedException {  
 
    Body body = new Body(new Head(new Face()));  
 
    Body body1 = (Body) body.clone();  
 
    System.out.println("body == body1 : " + (body == body1) );  
 
    System.out.println("body.head == body1.head : " +  (body.head == body1.head));  
 
    System.out.println("body.head.face == body1.head.face : " +  (body.head.face == body1.head.face));  
 
}

打印结果为:

body == body1 : false
body.head == body1.head : false
body.head.face == body1.head.face : true
那么,对Body对象来说,算是这算是深拷贝吗?其实应该算是深拷贝,因为对Body对象内所引用的其他对象(目前只有Head)都进行了拷贝,也就是说两个独立的Body对象内的head引用已经指向了独立的两个Head对象。但是,这对于两个Head对象来说,他们指向了同一个Face对象,这就说明,两个Body对象还是有一定的联系,并没有完全的独立。这应该说是一种不彻底的深拷贝。

如何进行彻底的深拷贝

对于上面的例子来说,怎样才能保证两个Body对象完全独立呢?只要在拷贝Head对象的时候,也将Face对象拷贝一份就可以了。这需要让Face类也实现Cloneable接口,实现clone方法,并且在在Head对象的clone方法中,拷贝它所引用的Face对象。修改的部分代码如下:

static class Head implements Cloneable{  
    public  Face face;  
 
    public Head() {}  
    public Head(Face face){this.face = face;}  
    @Override 
    protected Object clone() throws CloneNotSupportedException {  
        //return super.clone();  
        Head newHead = (Head) super.clone();  
        newHead.face = (Face) this.face.clone();  
        return newHead;  
    }  
}   
 
static class Face implements Cloneable{  
    @Override 
    protected Object clone() throws CloneNotSupportedException {  
        return super.clone();  
    }  
}

再次运行上面的示例,得到的运行结果如下:

body == body1 : false
body.head == body1.head : false
body.head.face == body1.head.face : false
这说名两个Body已经完全独立了,他们间接引用的face对象已经被拷贝,也就是引用了独立的Face对象。

此类推,如果Face对象还引用了其他的对象, 比如说Mouth,如果不经过处理,Body对象拷贝之后还是会通过一级一级的引用,引用到同一个Mouth对象。同理, 如果要让Body在引用链上完全独立, 只能显式的让Mouth对象也被拷贝。
到此,可以得到如下结论:如果在拷贝一个对象时,要想让这个拷贝的对象和源对象完全彼此独立,那么在引用链上的每一级对象都要被显式的拷贝。所以创建彻底的深拷贝是非常麻烦的,尤其是在引用关系非常复杂的情况下, 或者在引用链的某一级上引用了一个第三方的对象, 而这个对象没有实现clone方法, 那么在它之后的所有引用的对象都是被共享的。

最后

clone在平时项目的开发中可能用的不是很频繁,但是区分深拷贝和浅拷贝会让我们对java内存结构和运行方式有更深的了解。至于彻底深拷贝,几乎是不可能实现的,原因已经在上一节中进行了说明。深拷贝和彻底深拷贝,在创建不可变对象时,可能对程序有着微妙的影响,可能会决定我们创建的不可变对象是不是真的不可变。clone的一个重要的应用也是用于不可变对象的创建。
转载:
Java中对象的创建
java Clone使用方法详解

猜你喜欢

转载自blog.csdn.net/qq_35495339/article/details/89457819