`
wangyanlong0107
  • 浏览: 499847 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

【转】java 复制对象 ,对象序列化实现深度拷贝

    博客分类:
  • java
 
阅读更多

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

1 对象克隆
什么是"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操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

怎样应用clone()方法?


一个很典型的调用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;
    }





有三个值得注意的地方,一是希望能实现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?


下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例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);
    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);
    }
}


/** 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();

程序如下:

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接口,比较麻烦,那就继续学习下一个序列化方法。

2 对象序列化

所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。这个过程也可以通过网络实现,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。是不是很神奇。

也许你会说,只了解一点点,但从来没有接触过,其实未必如此。RMI、Socket、JMS、EJB你总该用过一种吧,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。

第一次使用Java的对象序列化是做某项目,当时要求把几棵非常复杂的树(JTree)及相应的数据保存下来(就是我们常用的保存功能),以便下次运行程序时可以继续上次的操作。

那时XML技术在网上非常的热,而且功能也强大,再加上树的结构本来就和XML存储数据的格式很像。作为一项对新技术比较有兴趣的我当然很想尝试一下。不过经过仔细分析,发现如果采用XML保存数据,后果真是难以想象:哪棵树的哪个节点被展开、展开到第几级、节点当前的属性是什么。真是不知该用A、B、C还是用1、2、3来表示。

还好,发现了Java的对象序列化机制,问题迎刃而解,只需简单的将每棵树的根节点序列化保存到硬盘上,下次再通过反序列化后的根节点就可以轻松的构造出和原来一模一样的树来。

其实保存数据,尤其是复杂数据的保存正是对象序列化的典型应用。最近另一个项目就遇到了需要对非常复杂的数据进行存取,通过使用对象的序列化,问题同样化难为简。

对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深层Clone和浅层Clone,如果你的对象非常非常复杂,假设有个100层的Collection(夸张了点),如果你想实现深层Clone,真是不敢想象,如果使用序列化,不会超过10行代码就可以解决。

还有就是Swing组件,如果你有两个很象很象(或是一模一样)的比较难以构造的Swing组件,你该怎么办,也许你想到了Clone,但是偏偏Java的Swing组件没有提供Clone方法。别急,使用序列化,6行代码搞定:

ByteArrayOutputStream
byteOut = new ByteArrayOutputStream(); 
ObjectOutputStream out
= new ObjectOutputStream(byteOut); 
out.writeObject(combo); 

ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
ObjectInputStream in
=new ObjectInputStream(byteIn); 
JComboBox comb2 = (JComboBox)in.readObject();

/** 

* @modifier: wang.yanlong

* @Title: cloneObj 

* @Description:用对象序列化实现对象克隆,用流自己画了个圈

* @param lsFeedBack

* @return

* @throws IOException

* @throws ClassNotFoundException   

* @date 2012-11-20 下午02:21:52

* @lastModified by wang.yanlong

*/  

   

private Object cloneObj(Object lsFeedBack) throws IOException,

ClassNotFoundException {

 ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); 

 ObjectOutputStream out = new ObjectOutputStream(byteOut); 

 out.writeObject(lsFeedBack); 


 ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 

 ObjectInputStream in = new ObjectInputStream(byteIn); 

 Object cloneObject = in.readObject();

 return cloneObject;

}

虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 

你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单态(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。 


    /**
     * Clone Object
     * @param obj
     * @return
     * @throws Exception
     */
    private Object cloneObject(Object obj) throws Exception{
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); 
        ObjectOutputStream out = new ObjectOutputStream(byteOut); 
        out.writeObject(obj); 
        
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); 
        ObjectInputStream in =new ObjectInputStream(byteIn);
        
        return in.readObject();
    }

参考资料:

1.http://www-128.ibm.com/developerworks/cn/java/l-jpointer/index.html

2.http://www.softexam.cn/eschool/details.asp?id=10930

 

分享到:
评论

相关推荐

    java不同对象及集合的多次复制

    - **序列化与反序列化**:将对象序列化为字节数组,然后再反序列化为新的对象,实现深拷贝。 3. **注解实现对象复制** - **Apache Commons BeanUtils库**:提供了`BeanUtils.copyProperties()`方法,可以快速实现...

    java 深度拷贝 复制 深度复制.zip

    在实际应用中,我们还可以考虑使用序列化和反序列化的方式实现深度拷贝,或者使用第三方库如Apache Commons Lang的`DeepClone`方法,它们提供了更简便的解决方案。但这里我们重点讨论了如何仅通过Java反射机制来实现...

    java对象复制克隆

    1. **实现Serializable接口**:这是通过序列化和反序列化实现深拷贝的方法。首先将对象转换为字节数组,然后从这个字节数组中反序列化出一个新的对象。这种方法简单,但不适用于所有对象,因为有些对象可能不适合...

    MyBatisDemo && JAVA把一个对象的全部属性复制到另一个相同的对象

    1. **实现Serializable接口**:利用序列化和反序列化来实现深拷贝。首先将对象序列化为字节数组,然后再将这个字节数组反序列化为新的对象。这种方法适用于所有实现了Serializable接口的对象,但效率较低,并且如果...

    java List 深度复制方法

    总结来说,Java中实现List的深度复制,可以采用序列化和反序列化的方法,或者手动复制每个元素。选择哪种方法取决于你的具体需求,例如对象是否支持序列化,以及性能和代码复杂性的考量。在处理可变对象时,确保深...

    Java利用序列化实现对象深度clone的方法

    总的来说,Java序列化提供了一种实现对象深度克隆的方法,适用于需要完整复制对象及其关联对象的情况。然而,这也带来了一些潜在问题,如性能影响、安全风险(因为序列化的对象可能被恶意用户反序列化以执行代码)...

    【IT十八掌徐培成】Java基础第16天-03.串行化-深度复制.zip

    在这个例子中,我们首先将对象序列化到字节数组,然后再从字节数组反序列化得到新的对象,这样就实现了深复制。需要注意的是,这种方法只适用于实现`Serializable`接口的类,且在反序列化时,需要处理可能抛出的`...

    java深度克隆

    浅拷贝只复制对象的基本属性,而深度拷贝不仅复制基本属性,还会复制对象所引用的对象,即递归地复制所有引用对象。本文将详细介绍Java中的深度克隆技术,包括其实现原理及应用场景。 #### 二、深度克隆的重要性 ...

    java的深度克隆的例子

    可以通过实现`Serializable`接口并进行序列化和反序列化,或者使用自定义的拷贝构造函数和工厂方法来实现。选择哪种方法取决于具体的应用场景和性能需求。理解并正确使用深度克隆在处理复杂的对象结构时尤其重要,...

    android 浅复制和深复制-Java Generic Deep Copy 篇

    而深复制(Deep Copy)则是不仅复制对象本身,还递归地复制它引用的对象,确保新对象与原对象及引用的对象完全独立。 在Android中,我们常常需要复制Parcelable或Serializable类型的对象,因为它们是Android系统...

    KryoJava序列化和克隆框架具有快速高效自动的特点

    6. **串行化与反串行化的API**:Kryo提供了简洁易用的API,使得开发者可以轻松地将对象序列化到字节数组、输入/输出流或者文件中,并且反序列化回来。 7. **兼容性**:Kryo虽然不是Java标准的序列化机制,但它能很...

    Java中的深拷贝(深复制)和浅拷贝(浅复制)介绍

    在Java中,实现深拷贝通常需要自定义复制逻辑,或者使用序列化和反序列化的方法。以下是一个深拷贝的例子: ```java class Professor implements Cloneable { String name; int age; Professor(String name, ...

    Java深复制与浅复制&Clone

    可以使用序列化(Serialization)和反序列化(Deserialization)来实现深复制,或者手动编写代码递归复制所有属性。例如: ```java public class MyClass implements Serializable { private String str; private...

    java对象拷贝常见面试题及应答汇总

    2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。 下面是一个使用序列化和反序列化实现克隆的示例代码: ```java import java.io.ByteArrayInputStream; import java.io....

    序列化(Serialization)实现深拷贝.md

    深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含多层...这时我们可以用序列化来实现对象的深拷贝。

    数据复制代码JAVA

    要进行深度拷贝(即完全复制对象的所有属性),需实现`Cloneable`接口并重写`clone()`方法。 2. **Arrays类的深拷贝** Java的`Arrays`类提供了`copyOf()`和`copyOfRange()`方法,用于数组的数据复制。这两个方法...

    深入JAVA对象深度克隆的详解

    要实现深度克隆,首先,你的类需要实现`java.io.Serializable`接口,这是Java中用于序列化的标记接口。然后,你可以定义一个名为`copy()`的方法来执行深度克隆,如下所示: ```java public class YourClass ...

    java反射以及复制一个bean的值到另一个bean中。

    复制一个bean的值到另一个bean中,通常是为了实现对象之间的深度克隆。Java没有内置的深拷贝机制,但可以通过反射实现。假设我们有两个 `Person` 类的实例 `person1` 和 `person2`,我们想要将 `person1` 的属性值...

Global site tag (gtag.js) - Google Analytics