教程集 www.jiaochengji.com
教程集 >  脚本编程  >  java  >  正文 java中对象的浅复制和深复制笔记

java中对象的浅复制和深复制笔记

发布时间:2016-10-21   编辑:jiaochengji.com
教程集为您提供java中对象的浅复制和深复制笔记等资源,欢迎您收藏本站,我们将为您提供最新的java中对象的浅复制和深复制笔记资源
java中对象的复制我相信很多朋友和小编一样头一次听说了,今天我就来为各位介绍一下java中对象的浅复制和深复制笔记,希望这些文章对各位有帮助。

在面向对象的语言中,如Java/Python,对象的复制有两种形式:浅复制和深复制

一、浅复制

   浅复制只是将原对象的引用备份了一份给新的变量,实际两者指向的是同一个对象。在Python中,字典、列表、元祖等都是对象类型

>>> person=['name',['savings',100.00]]
>>> hubby=person[:]
>>> wifely=list(person)

>>> [id(x) for x in person,hubby,wifely]
[42524024, 42545920, 42523744]
>>>
   person是一个列表对象,分割之后赋给hubby,将person显示转为列表对象赋给wifely。这种简单的赋值,就是对象的浅复制—只是copy了一份对象的引用,而实际三个变量均指向同一个对象,对任何一个引用的修改,都会映射到三个对象上。

>>> hubby[0]='joe'
>>> wifely[0]='jane'
>>> hubby,wifely
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>> hubby[1][1]=50.00
>>> hubby,wifely
(['joe', ['savings', 50.0]], ['jane', ['savings', 50.0]])
看看person对象的输出:

>>> person
['name', ['savings', 50.0]]
person、hubby、wifely的第二个元素是一个列表对象,当hubby修改该对象的数据时,其影响也同时映射到person、wifely。但为什么修改了第一个,而没有同步映射呢?因为第一个元素是字符串类型,是不可变的;第二个元素是列表对象,是可变的。所以在浅复制时,每次修改字符串内容都是新建了一个对象,在Java中也是如此,而对象只是复制了一个引用。这一点,可以通过id()函数验证,它标识对象的“身份”,就想人的身份证一样

#修改值之前
>>>[id(x) for x in hubby]
[9919616,11826320]
>>>[id(x) for x in wifely]
[9919616,11826320]

#修改值之后
>>>[id(x) for x in hubby]
[12092832,11826320]
>>>[id(x) for x in wifely]
[12191712,11826320]
从上面对比可知,对字符串的修改都会创建新对象,而在浅复制中,对象的“身份”没有变化,并没有创建新的对象。如果要使person、wifely、hubby不共享一份对象数据呢?那就要进行深复制

二、深复制

   在Python中,实现深复制比较容易,依靠copy模块就行

>>> person=['name',['savings',100.00]]
>>> hubby=person    #浅复制
>>> import copy     #引入模块
>>> wifely=copy.deepcopy(person)  #深复制
>>> hubby[0]='joe'
>>> wifely[0]='jane'
>>> hubby,wifely
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>>

 对于字符串的修改各自为政,互不影响。然后,按上述浅复制中的方式同样修改hubby,看会不会影响到wifely

>>> hubby[1][1]=50.00
>>> hubby,wifely
(['joe', ['savings', 50.0]], ['jane', ['savings', 100.0]])
>>>

只是改变了hubby中的内容,wifely没有做出相应的映射。可以用id()验证四个对象是不同的

>>> [id(x) for x in hubby]
[41809800, 41885448]
>>> [id(x) for x in wifely]
[41968832, 41917536]
>>>

对于Java中的深复制就比较复杂我们可以看下面的例子

如何操作这个"指针",更不用象在操作C++的指针那样胆战心惊。唯一要多多关心的是在给函数传递对象的时候。

package com.zoer.src; 
 
public class ObjRef { 
    Obj aObj = new Obj(); 
    int aInt = 11; 
 
    public void changeObj(Obj inObj) { 
        inObj.str = "changed value"; 
    } 
 
    public void changePri(int inInt) { 
        inInt = 22; 
    } 
 
    public static void main(String[] args) { 
        ObjRef oRef = new ObjRef(); 
 
        System.out.println("Before call changeObj() method: " oRef.aObj); 
        oRef.changeObj(oRef.aObj); 
        System.out.println("After call changeObj() method: " oRef.aObj); 
 
        System.out.println("==================Print Primtive================="); 
        System.out.println("Before call changePri() method: " oRef.aInt); 
        oRef.changePri(oRef.aInt); 
        System.out.println("After call changePri() method: " oRef.aInt); 
 
    } 


package com.zoer.src; 
 
public class Obj { 
 
    String str = "init value"; 
 
    public String toString() { 
        return str; 
    } 

      这段代码的主要部分调用了两个很相近的方法,changeObj()和changePri()。唯一不同的是它们一个把对象作为输入参数,另一个把Java中的基本类型int作为输入参数。并且在这两个函数体内部都对输入的参数进行了改动。看似一样的方法,程序输出的结果却不太一样。changeObj()方法真正的把输入的参数改变了,而changePri()方法对输入的参数没有任何的改变。
      从这个例子知道Java对对象和基本的数据类型的处理是不一样的。和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的函数体内部的操作都是针对这个拷贝的操作,函数执行结束后,这个局部变量也就完成了它的使命,它影响不到作为输入参数的变量。这种方式的参数传递被称为"值传递"。而在Java中用对象作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。
      除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递"。就是类似于给变量再起一个别名。两个名字都指向内存中的同一个对象。
      在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
      Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
      怎样应用clone()方法?

      一个很典型的调用clone()代码如下:

public 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; 
    } 

      有三个值得注意的地方,一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。另一个值得请注意的是重载了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。
      应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。
       那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
      以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。
      什么是影子clone?

package com.zoer.src; 
 
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); 
 
    public Object clone() { 
        CloneB o = null; 
        try { 
            o = (CloneB) super.clone(); 
        } catch (CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
        return o; 
    } 

 
public class ObjRef { 
    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); 
    } 

       输出结果:
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();

package com.zoer.src; 
 
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); 
    } 

输出结果:
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中String和StringBuffer的区别
       应该说明的是,这里不是着重说明String和StringBuffer的区别,但从这个例子里也能看出String类的一些与众不同的地方。
       下面的例子中包括两个类,CloneC类包含一个String类型变量和一个StringBuffer类型变量,并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1,然后调用c1的clone()方法生成c1的拷贝c2,在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果:

package com.zoer.src; 
 
class CloneC implements Cloneable { 
    public String str; 
    public StringBuffer strBuff; 
 
    public Object clone() { 
        CloneC o = null; 
        try { 
            o = (CloneC) super.clone(); 
        } catch (CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
        return o; 
    } 
 

 
public class StrClone { 
    public static void main(String[] a) { 
        CloneC c1 = new CloneC(); 
        c1.str = new String("initializeStr"); 
        c1.strBuff = new StringBuffer("initializeStrBuff"); 
        System.out.println("before clone,c1.str = " c1.str); 
        System.out.println("before clone,c1.strBuff = " c1.strBuff); 
 
        CloneC c2 = (CloneC) c1.clone(); 
        c2.str = c2.str.substring(0, 5); 
        c2.strBuff = c2.strBuff.append(" change strBuff clone"); 
        System.out.println("================================="); 
        System.out.println("after clone,c1.str = " c1.str); 
        System.out.println("after clone,c1.strBuff = " c1.strBuff); 
        System.out.println("================================="); 
        System.out.println("after clone,c2.str = " c2.str); 
        System.out.println("after clone,c2.strBuff = " c2.strBuff); 
    } 

执行结果:

<span style="font-family:'Microsoft YaHei';"><span style="font-size:16px;">before clone,c1.str = initializeStr 
before clone,c1.strBuff = initializeStrBuff 
================================= 
after clone,c1.str = initializeStr 
after clone,c1.strBuff = initializeStrBuff change strBuff clone 
================================= 
after clone,c2.str = initi 
after clone,c2.strBuff = initializeStrBuff change strBuff clone 
</span></span> 
        打印的结果可以看出,String类型的变量好象已经实现了深度clone,因为对c2.str的改动并没有影响到c1.str!难道Java把Sring类看成了基本数据类型?其实不然,这里有一个小小的把戏,秘密就在于c2.str = c2.str.substring(0,5)这一语句!实质上,在clone的时候c1.str与c2.str仍然是引用,而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候,它作用相当于生成了一个新的String类型,然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类(immutable class),在所有String类中的函数都不能更改自身的值。

您可能感兴趣的文章:
jQuery中json对象的复制方式介绍(数组及对象)
Golang笔记:语法,并发思想,web开发,Go微服务相关
java中对象的浅复制和深复制笔记
python中浅拷贝与深拷贝模块的使用
python如何给list赋值
python深拷贝和浅拷贝的区别是什么
Illustrator制作喷溅效果的艺术字效果教程
Illustrator打造柔和幻美的水彩笔刷制作教程分享
PHP设计模式之:原型模式学习笔记
《JavaScript模式》读书笔记

[关闭]
~ ~