关于java对象复制

在编码过程经常会碰到将一个对象传递给另一个对象,java中对于基本型变量采用的是值传递,而对于对象比如bean传递时采用的是引用传递也就是地址传递,而很多时候对于对象传递我们也希望能够象值传递一样,使得传递之前和之后有不同的内存地址,在这种情况下就可以clone一个新的对象来用(简单快捷)。

什么时候使用克隆对象技术

如:有一个对象A,在某一时刻A中已经包含了一些有效值,A是用来保存从数据库得到很多数据的一个对象。此时可能会需要一个和A完全相同新对象B,B用来修改A里面的某一个属性的值,仅仅是用来负责显示一下,不需要使得A.也受到影响。并且此函数的返回类型是A类型的。并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。实现clone()方法是不错的选择。

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

怎样应用clone()方法?

实现clone方法的步骤()

(1)实现Cloneable接口

(2)重载Object类中的clone()方法,重载时需定义为public

(3)在重载方法中,调用super.clone()

class CloneClass implements Cloneable{ 
    public int aInt; 
    public Object clone(){ 
        CloneClass o = null; 
        try{ 
            o = (CloneClass)super.clone(); 
        }catch(CloneNotSupportedException e){ 
            e.printStackTrace(); 
        } 
        return o; 
    } 
}

浅克隆与深克隆(影子克隆)

       克隆(默认就是浅克隆)就是复制一个对象的复本.若只需要复制对象的字段值(对于基本数据类型,如:int,long,float等,则复制值;对于复合数据类型仅复制该字段值,如数组变量则复制地址,对于对象变量则复制对象的reference。

下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化CloneB 类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出:

package clone; 
class UnCloneA { 
    private int i; 
    public UnCloneA(int ii) { i = ii; } 
    public void doubleValue() { i *= 2; } 
    public String toString() { 
        return Integer.toString(i); 
    } 
} 
class CloneB implements Cloneable{ 
    public int aInt; 
    public UnCloneA unCA = new UnCloneA(111); 
    publicCloneB clone(){ 
        CloneB o = null; 
        try{ 
            o = (CloneB)super.clone(); 
        }catch(CloneNotSupportedException e){ 
            e.printStackTrace(); 
        } 
        return o; 
    } 
} 
public class CloneMain { 
    public static void main(String[] a){ 
        CloneB b1 = new CloneB(); 
        b1.aInt = 11; 
        System.out.println("before clone,b1.aInt = "+ b1.aInt); 
        System.out.println("before clone,b1.unCA = "+ b1.unCA); 
                 
        CloneB b2 = (CloneB)b1.clone(); 
        b2.aInt = 22; 
        b2.unCA.doubleValue(); 
        System.out.println("================================="); 
        System.out.println("after clone,b1.aInt = "+ b1.aInt); 
        System.out.println("after clone,b1.unCA = "+ b1.unCA); 
        System.out.println("================================="); 
        System.out.println("after clone,b2.aInt = "+ b2.aInt); 
        System.out.println("after clone,b2.unCA = "+ b2.unCA); 
    } 
}
/** RUN RESULT: 
before clone,b1.aInt = 11 
before clone,b1.unCA = 111 
================================= 
after clone,b1.aInt = 11 
after clone,b1.unCA = 222 
================================= 
after clone,b2.aInt = 22 
after clone,b2.unCA = 222 
*/

输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,

int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,

也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。

相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!(也就是对对象中的对象克隆失败)

从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。

对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。也就没有达到克隆的效果。

大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。

怎么进行深度clone?

深克隆与浅克隆的区别在于对复合数据类型的复制。若对象中的某个字段为复合类型,在克隆对象的时候,需要为该字段重新创建一个对象。

把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone()

程序如下:

class UnCloneA implements Cloneable{ 
    private int i; 
    public UnCloneA(int ii) { i = ii; } 
    public void doubleValue() { i *= 2; } 
    public String toString() { 
        return Integer.toString(i); 
    } 
    public Object clone(){ 
        UnCloneA o = null; 
        try{ 
            o = (UnCloneA)super.clone(); 
        }catch(CloneNotSupportedException e){ 
            e.printStackTrace(); 
        } 
        return o; 
    } 
} 
class CloneB implements Cloneable{ 
    public int aInt; 
    public UnCloneA unCA = new UnCloneA(111); 
    public Object clone(){ 
        CloneB o = null; 
        try{ 
            o = (CloneB)super.clone(); 
        }catch(CloneNotSupportedException e){ 
            e.printStackTrace(); 
        } 
        o.unCA = (UnCloneA)unCA.clone(); 
        return o; 
    } 
} 
public class CloneMain { 
    public static void main(String[] a){ 
        CloneB b1 = new CloneB(); 
        b1.aInt = 11; 
        System.out.println("before clone,b1.aInt = "+ b1.aInt); 
        System.out.println("before clone,b1.unCA = "+ b1.unCA); 
                 
        CloneB b2 = (CloneB)b1.clone(); 
        b2.aInt = 22; 
        b2.unCA.doubleValue(); 
        System.out.println("================================="); 
        System.out.println("after clone,b1.aInt = "+ b1.aInt); 
        System.out.println("after clone,b1.unCA = "+ b1.unCA); 
        System.out.println("================================="); 
        System.out.println("after clone,b2.aInt = "+ b2.aInt); 
        System.out.println("after clone,b2.unCA = "+ b2.unCA); 
    } 
}
/** RUN RESULT: 
before clone,b1.aInt = 11 
before clone,b1.unCA = 111 
================================= 
after clone,b1.aInt = 11 
after clone,b1.unCA = 111 
================================= 
after clone,b2.aInt = 22 
after clone,b2.unCA = 222 
*/

可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。

-----------------------------------------------------------------以下的话,暂时没遇到过,就看看-----------------------------------------

不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成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对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。

通过以上我们可以看出在某些情况下,我们可以利用clone方法来实现对象只见的复制,但对于比较复杂的对象(比如对象中包含其他对象,其他对象又包含别的对象.....)这样我们必须进行层层深度clone,每个对象需要实现cloneable接口。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

如果克隆对象里面有list,怎么深度克隆?

怎么深度克隆集合

    public Stream findStreamrulesByStreamId(String streamId) { 
        Stream stream = streamCache.getStreamOfStreamId(streamId); 
        Stream streamClone = (Stream) stream.clone(); 
         
        List<StreamRule> streamRulesInCache = stream.getStreamrules(); 
        List<StreamRule> copy = new ArrayList<StreamRule>(); 
        Iterator<StreamRule> iteratorCopy = streamRulesInCache.iterator(); 
        while (iteratorCopy.hasNext()) { 
        	copy.add(iteratorCopy.next().clone());			 
		} 
        streamClone.setStreamrules(copy); 
        List<StreamRule> streamRules = streamClone.getStreamrules(); 
         
        if (null == streamRules) return streamClone; 
        formatOldData(streamRules); 
        Iterator<StreamRule> iterator = streamRules.iterator(); 
        StreamRule streamRule; 
        while (iterator.hasNext()) { 
            streamRule = iterator.next(); 
            if (streamRule.getRule_type() == stream_rule_type.getTYPE_FACILITY()) { 
                iterator.remove(); 
            } 
        } 
        return streamClone; 
    }

解释:

Stream bean里面包含了一个 List<StreamRule> 的属性。可以看出,

Stream stream = streamCache.getStreamOfStreamId(streamId); 这个对象是用来获得数据的,且不希望被改变,

但是,页面显示又要改变数据,若是引用传递的话,那么这个对象里面的值就要被改变。

所以,克隆就可以很好的解决这个问题。

其中(Stream 和 StreamRule 两个bean实现了Cloneable接口,格式在最上面的例子可参考)

后面这些话是比较重要的:(易于理解)

这就是Java中如何克隆集合的内容。现在我们知道拷贝构造函数或者ListSet等各种集合类的addAll()方法仅仅创建了集合的浅拷贝,而且原始集合和克隆集合指向相同的对象。为避免这个问题,应该深克隆集合,遍历集合克隆每个元素。尽管这要求集合中的对象必须支持深克隆操作。

各个集合类提供的拷贝构造函数作为克隆ListSetArrayListHashSet或者其他集合实现的方法。需要记住的是,Java集合的拷贝构造函数只提供浅拷贝而不是深拷贝,这意味着存储在原始List和克隆List中的对象是相同的,指向Java堆内存中相同的位置。

(简单注意)

  1. 在克隆java对象的时候不会调用构造器
  2. java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含引用类型,那么原始对象和克隆都将指向相同的引用内容,这是很危险的,因为发生在可变的字段上任何改变将反应到他们所引用的共同内容上。为了避免这种情况,需要对引用的内容进行深度克隆。
  3. 克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.

发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

HTML关于post和get的区别以及缓存问题的理解知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。