java的对象复制 --整理总结版


Java 中的赋值操作符与 C++ 中的不一样。在 C++ 中,这条语句:bc2 = bc1;将一个名为 bc1 的对象的所有数据都拷贝到名为 bc2 的对象中。也就是说这条语句执行后,程序中有两个含有相同数据的对象。然而在 Java 中,这条相同的赋值语句只向 bc2 中拷贝了 bc1 指向的存储地址,现在 bc1 和 bc2 实际上指的是同一个对象,它们都是这个对象的引用。这样大大提高了内存使用效率,同时也容易让一些对内存了解不深的朋友带来一些使用上的错误。比如 bc1.add(25);buc2.add(20);执行之后 bc1 增加了 45。而作者本意可能是只是让 bc1 增加 25,bc2 增加 20 而已。这说明了,在 Java 中,bc2 = bc1; 并不是真正意义上的复制。那么在 Java 中如何进行对象复制呢?作者结合 Java 数据结构相关知识,总结了一些项目中的经验,希望可以和大家共同探讨一下这个问题。

        没有使用对象复制的代码:

        银行帐户源代码 BankAccount.java:

    package clone;  
    public class BankAccount{  
        private double balance;  
        public BankAccount(double ini){  
            this.balance = ini;  
        }  
          
        public double getBalance() {  
            return balance;  
        }  
        public void setBalance(double balance) {  
            this.balance = balance;  
        }  
        public void add(double give){  
            this.balance += give;  
        }  
          
        public void redu(double give){  
            this.balance -= give;  
        }  
    }  

  程序入口 TestClone.java:


    package clone;  
    public class TestClone {  
        public static void main(String[] args) {  
            BankAccount bc1 = new BankAccount(1000.0);  
            BankAccount bc2 = bc1;  
            bc1.add(25);  
            bc2.add(20);  
            System.out.println("用户 1 的帐户余额为:" + bc1.getBalance());  
            System.out.println("用户 2 的帐户余额为:" + bc2.getBalance());  
        }  
    }  

执行 TestClone.java,打印结果如下:
用户 1 的帐户余额为:1045.0
用户 2 的帐户余额为:1045.0

        这下用户 2 要哭了:自己辛辛苦苦攒的一点钱都存别人帐户里边去了。怎么解决这个问题呢?用户 2 不应该使用等号,而应该进行对象复制。那么在 Java 中怎样进行对象复制呢?有两种办法。
        Java 中对象的复制办法一        一开始就创建两个不同的对象,然后分别拷贝每一个字段。注意:等号是不起复制作用的!这里说的拷贝并非用等号进行,而是手工复制。代码说明如下。
        银行帐户源代码不变 BankAccount.java:

    package clone;  
    public class BankAccount{  
        private double balance;  
        public BankAccount(double ini){  
            this.balance = ini;  
        }  
          
        public double getBalance() {  
            return balance;  
        }  
        public void setBalance(double balance) {  
            this.balance = balance;  
        }  
        public void add(double give){  
            this.balance += give;  
        }  
          
        public void redu(double give){  
            this.balance -= give;  
        }  
    }  

  在程序入口 TestClone.java 中进行对象复制:

    package clone;  
    public class TestClone {  
        public static void main(String[] args) {  
            BankAccount bc1 = new BankAccount(1000.0);  
            BankAccount bc2 = new BankAccount(1000.0);  
            bc1.add(25);  
            bc2.add(20);  
            System.out.println("用户 1 的帐户余额为:" + bc1.getBalance());  
            System.out.println("用户 2 的帐户余额为:" + bc2.getBalance());  
        }  
    }  

执行 TestClone.java,打印结果如下:
用户 1 的帐户余额为:1025.0
用户 2 的帐户余额为:1020.0

        用户 2 终于把自己挣的血汗钱存入了自己的帐户里去了。但是 Java 程序员要哭了:这里只是一个简单的例子,如果对象比较复杂,每次都要复制,而且还要处处考虑是不是又进行赋值引用了?这岂不麻烦?为了克服这个问题,Java 引入了克隆的概念。
        Java 中对象的复制办法二        使用克隆进行对象复制。Java API 里解释:java.lang.Object.clone() 可以创建一个当前实例的拷贝。前提是当前实例的对象必须实现 java.lang.Cloneable 接口,然后再重载 java.lang.Object 的 clone 方法。代码说明如下。
        银行帐户源代码 BankAccount.java:

    package clone;  
    public class BankAccount implements Cloneable{  
        private double balance;  
        public BankAccount(double ini){  
            this.balance = ini;  
        }  
          
        protected Object clone(){  
            BankAccount bankAccount = null;  
            try {  
                bankAccount = (BankAccount)super.clone();  
            } catch (CloneNotSupportedException e) {  
                e.printStackTrace();  
            }  
            return bankAccount;  
        }  
          
        public void add(double give){  
            this.balance += give;  
        }  
          
        public void redu(double give){  
            this.balance -= give;  
        }  
          
        public double getBalance() {  
            return balance;  
        }  
        public void setBalance(double balance) {  
            this.balance = balance;  
        }  
    }  

  程序入口 TestClone.java 如下:
    package clone;  
    public class TestClone {  
        public static void main(String[] args) {  
            BankAccount bc1 = new BankAccount(1000.0);  
            BankAccount bc2 = (BankAccount) bc1.clone();  
            bc1.add(25);  
            bc2.add(20);  
            System.out.println("用户 1 的帐户余额为:" + bc1.getBalance());  
            System.out.println("用户 2 的帐户余额为:" + bc2.getBalance());  
        }  
    }  

执行 TestClone.java,打印结果如下:
用户 1 的帐户余额为:1025.0
用户 2 的帐户余额为:1020.0

        这下用户 2 把钱存进了自己的帐户,而 Java 程序员也不用头疼了。皆大欢喜。

以上部分转自:http://blog.csdn.net/defonds/article/details/5114486


通过以上的例子说明了在java中如何进行对象的数据。 但代码例子中,对象BankAccount中的属性是double类型,即是基本数据类型(primitive),这种情况下,可以直接

在重载的clone方法里面调用super.clone().进行引用的复制。

但是如果被克隆的对象的属性不是基本数据类型(primitive),而是字符串String呢?   --(我们知道在java中String属于特殊引用类型,不可变类型)


看如下例子:


import org.junit.Assert;

    class User implements Cloneable {  
        String name;  
        int age;  
      
        @Override  
        public Object clone() throws CloneNotSupportedException {  
            return super.clone();  
        }  
    }  
    
    
public class DeepClone{  
    public static void main(String[] a){  
        // user.  
        User user = new User();  
        user.name = "user";  
        user.age = 20;  
          
        // copy  
        User copy = null;
		try {
			copy = (User) user.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  
         
		//copy 和user不相等
		 System.out.println(copy==user);
		 
        // age因为是primitive,所以copy和原型是相等且独立的。  
		System.out.println("user.age = "+user.age+", copy.age = "+copy.age);
        
        copy.age = 30;  
        // 改变copy不影响原型。  
        System.out.println("user.age = "+user.age+", copy.age = "+copy.age);
          
        // name因为是引用类型,所以copy和原型的引用是同一的。 
        System.out.println(copy.name == user.name);
        
        // String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。  
        // 改变引用新的对象不会影响原型。  
        copy.name = "newname";  
        
        System.out.println("user.name = "+user.name+", copy.name = "+copy.name);
        
    }  
}  


结果为:

false
user.age = 20, copy.age = 20
user.age = 20, copy.age = 30
true
user.name = user, copy.name = newname


user和copy是两个引用,达到了复制的目的,但是对于内部属性name,他们的引用确是同一个,之所以没办法通过对copy.name的字符串操作改变这个字符串,是因为String类型的特殊性。String类型的引用在栈上,数值在堆里。更改赋值 ,String重新申请一块内存。

可见,在考虑clone时,primitive和不可变对象类型是可以同等对待


那么,如果被克隆对象的属性是其他引用类型呢?

接着看例子:

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);  
    
    //重写clone()方法
    public Object 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);  
    }  
	
}

结果为:

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后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。【请比较为什么String类型的克隆指向的不是同一个内存】


这种克隆被称为影子克隆


要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。【请忽略上一个例子中的DeepClone类名,这个类名是不准确的】

怎么进行深度clone?


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

程序如下:


package clone.ext;
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接口,比较麻烦


以上转自:http://liran-email.iteye.com/blog/550249

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值