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

当需要对同一个类,生成多个对象时。一般有三种方法:new()、clone()、以及序列化复制

new和clone的区别,简单的说一下:

new的操作为 分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

由此可见,clone的速度是大于new的,尤其是在对大对象操作时。

然而,我们知道拷贝分为深拷贝和浅拷贝

浅拷贝:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。

深拷贝:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。

显然,浅拷贝是不安全的,可不是我们想要的。

下面我们来看一下clone方法和序列化方法,对于深拷贝浅拷贝的区别差异:

先看一下clone方法的浅拷贝和深拷贝。

我们先定义一个Person类,用作引用类型。

public class Person{
    int i =0;
}

然后在写一个需要拷贝对象的类。TextClass。并实现Cloneable接口的clone方法。

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对象。

        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,无误。

1: person比较
true

那么该如何实现深复制呢?毕竟深复制才是我们想要的。这就必须对引用类型进行进一步的拷贝和赋值。修改之后的代码为:

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;
    }
}
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来复制,两者指向的地址并不一样。结果,无误:

1: person比较
false

下面我们来看一下使用序列化的方式来复制时的情况

首先写一个序列化方法类:

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;以保证能够序列化

public class TextClass implements Serializable,Cloneable{
    ...
}
public class Person implements Cloneable,Serializable{
   ...
}
测试代码为:
        TextClass text2 = CloneUtils.clone(text1);
        System.out.println("1: person比较");
        System.out.println(text1.getPerson()==text2.getPerson());

因为序列化是将对象写进二进制流中,然后经过反序列化,再从中读取出来,所以应该是完全不一样的两个对象。

故而是一个彻底的深复制。实验结果因为为 false。运行也是无误

1: person比较
false

至此,我们可以小结一下,clone方法只能复制基本类型,对于引用类型它只是浅拷贝。遇到引用类型,我们必须要对该属性(person)进行单独的clone。当引用类型太多时,会多出很大的工作量。也真是因为这样,所以引用类型一旦是final修饰时,我们就不能使用clone方法了。

序列化和反序列化,则是彻底的深拷贝。

在实验的时候,还发现clone拷贝String变量的问题。先不说是为什么,直接上测试代码。

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

 大家猜一下实验结果是什么?是不是想说:

2: name比较
true
zhou
zhou
xu
xu
3: name比较
true

如果,你是这么想的。那么说明你关于clone是懂了。但是你运行后会发现,结果不是这样的。

哈哈哈,惊喜不,意外不。

执行的结果是这样的:

2: name比较
true
zhou
zhou
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”的地址。

猜你喜欢

转载自blog.csdn.net/m0_37128231/article/details/81912327
今日推荐