您的位置:首页 > 编程语言 > Java开发

Java - 对象复制,cloneable与序列化复制的区别

2018-08-21 19:46 288 查看

当需要对同一个类,生成多个对象时。一般有三种方法: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”的地址。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐