Java - 对象复制,cloneable与序列化复制的区别
当需要对同一个类,生成多个对象时。一般有三种方法:new()、clone()、以及序列化复制
new和clone的区别,简单的说一下:
new的操作为 分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
由此可见,clone的速度是大于new的,尤其是在对大对象操作时。
然而,我们知道拷贝分为深拷贝和浅拷贝。
浅拷贝:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。
显然,浅拷贝是不安全的,可不是我们想要的。
下面我们来看一下clone方法和序列化方法,对于深拷贝浅拷贝的区别差异:
先看一下clone方法的浅拷贝和深拷贝。
我们先定义一个Person类,用作引用类型。
[code]public class Person{ int i =0; }
然后在写一个需要拷贝对象的类。TextClass。并实现Cloneable接口的clone方法。
[code]public class TextClass implements Cloneable { private int age; private String name; final int i=9; private Person person; public TextClass clone(){ TextClass textClass = null; try { textClass =(TextClass)super.clone(); }catch (CloneNotSupportedException e) { System.out.println(e); } return textClass; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person getPerson() { return person; } public void setPerson( Person person) { this.person = person; } }
现在,我们执行代码来看看前后clone对象的值,是否相等。(完整代码在最后一起给出,此处为了方便理解,逐语句的验证输出)。我们先实例化了一个text1对象,作为原对象。之后通过clone来复制出一个textClass1对象。
[code] TextClass text1 =new TextClass(); text1.setAge(40); text1.setName(new String("zhou")); Person person = new Person(); text1.setPerson(person); TextClass textClass1 = text1.clone(); System.out.println("1: person比较"); System.out.println(text1.getPerson()==textClass1.getPerson());
分析,此次是浅复制,我们没有对Person进行进一步的拷贝,赋值。所以输出因为该true。
输出结果为:true,无误。
[code]1: person比较 true
那么该如何实现深复制呢?毕竟深复制才是我们想要的。这就必须对引用类型进行进一步的拷贝和赋值。修改之后的代码为:
[code]public class Person implements Cloneable{ int i =0; public Person clone(){ Person person =null; try { person =(Person)super.clone(); }catch(CloneNotSupportedException e){ e.printStackTrace(); } return person; } }
[code]public class TextClass implements Cloneable { private int age; private String name; final int i=9; private Person person; public TextClass clone(){ TextClass textClass = null; try { textClass =(TextClass)super.clone(); textClass.setPerson(this.getPerson().clone()); //加入的语句 }catch (CloneNotSupportedException e) { System.out.println(e); } return textClass; } // set get... }
再次执行,上面的测试代码,如果正确,应该为false。因为此时的person已用了clone来复制,两者指向的地址并不一样。结果,无误:
[code]1: person比较 false
下面我们来看一下使用序列化的方式来复制时的情况
首先写一个序列化方法类:
[code]public class CloneUtils { @SuppressWarnings("unchecked") public static <T extends Serializable> T clone(T obj){ T cloneObj = null; try { //写入字节流 ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream obs = new ObjectOutputStream(out); obs.writeObject(obj); obs.close(); //分配内存,写入原始对象,生成新对象 ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(ios); //返回生成的新对象 cloneObj = (T) ois.readObject(); ois.close(); } catch (Exception e) { e.printStackTrace(); } return cloneObj; } }
在进行测试之前,我们先要将Person和TextClass类实现接口 Serializable;以保证能够序列化
[code]public class TextClass implements Serializable,Cloneable{ ... } public class Person implements Cloneable,Serializable{ ... }
测试代码为:
[code] TextClass text2 = CloneUtils.clone(text1); System.out.println("1: person比较"); System.out.println(text1.getPerson()==text2.getPerson());
因为序列化是将对象写进二进制流中,然后经过反序列化,再从中读取出来,所以应该是完全不一样的两个对象。
故而是一个彻底的深复制。实验结果因为为 false。运行也是无误
[code]1: person比较 false
至此,我们可以小结一下,clone方法只能复制基本类型,对于引用类型它只是浅拷贝。遇到引用类型,我们必须要对该属性(person)进行单独的clone。当引用类型太多时,会多出很大的工作量。也真是因为这样,所以引用类型一旦是final修饰时,我们就不能使用clone方法了。
序列化和反序列化,则是彻底的深拷贝。
在实验的时候,还发现clone拷贝String变量的问题。先不说是为什么,直接上测试代码。
[code] System.out.println("2: name比较"); System.out.println(text1.getName()==textClass1.getName()); text1.setName("xu"); System.out.println(text1.getName()); System.out.println(textClass1.getName()); System.out.println("3: name比较"); System.out.println(text1.getName()==textClass1.getName());
大家猜一下实验结果是什么?是不是想说:
[code]2: name比较 true zhou zhou xu xu 3: name比较 true
如果,你是这么想的。那么说明你关于clone是懂了。但是你运行后会发现,结果不是这样的。
哈哈哈,惊喜不,意外不。
执行的结果是这样的:
[code]2: name比较 true zhou zhou 4000 xu zhou 3: name比较 false
前面四个输出是没有什么问题的。第一个true,是因为String为引用类型,是浅复制。
问题出现在,对text1.setName(“xu”);之后,既然是浅复制为什么textClass1.getName()不为“xu”;
这是因为String底层存放的数据是final的!
当你执行text1.setName(“xu”)、并不是将text1.Name指向的“zhou”改成“xu”;而是生成一个新的String对象“xu”;让text1.Name指向“xu”;而textClass1.getName()=“zhou”;也证明了这一点,原来地址上的数据并没有被修改。
最后一个false ,想必到现在你也知道了,是的,因为text1.Name指向的地址偷偷地变了,不再是原来存放“zhou”的地址,
而是新String对象“xu”的地址。
阅读更多- Java深拷贝除了通过实现Cloneable接口,另外还可以通过序列化实现对象的拷贝。
- 关于Java对象复制(Clone、深度Clone以及序列化与反序列化的使用)
- 构造器陷阱(序列化恢复Java对象,clone复制Java对象,无限递归的构造器)
- java 的对象拷贝(有深浅拷贝两种方式,深拷贝实现的两种方式(逐层实现cloneable接口,序列化的方式来实现))
- 对象的深复制与浅复制 实现Cloneable接口实现深复制 序列化实现深复制
- Java 基础数据类型 和 深度克隆对象的2种方法(实现Cloneable接口或者实现对象序列化)
- java中equals和==之间的区别?clone方法的作用,及其为什么要使用clone方法?如何使用clone复制对象?以及深克隆浅克隆
- java语言——对象的深复制(deep clone)与浅复制(shallow clone)
- 实现Cloneable接口,让类的对象可以复制
- java 重写接口Cloneable的Clone方法 拷贝对象
- java的clone()方法和Java Serializable复制新对象,而不影响原来对象
- java对象 深度克隆(不实现Cloneable接口)和浅度克隆
- 【Java】Java中复制/克隆(Clone)一个对象
- 以clone和序列化方式实现对象复制
- php5对象复制、clone、浅复制与深复制的区别与介绍
- java中复制对象通过反射或序列化
- java对象 深度克隆(不实现Cloneable接口)和浅度克隆
- JAVA Clone复制对象
- JAVA 复制对象时为什么要用克隆clone()而不用“=”的原因
- Java编程中实现Cloneable接口,让类的对象可以复制