返回 登录
0

Java深克隆与浅克隆

学习Java的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:492139965 我们一起学Java!

所有的Java类都继承自java.lang.Object,而Object类提供了一个clone()方法,可以将一个Java对象复制一份。需要注意的是能够实现克隆的Java类必须实现一个标示接口Cloneable,表示这个Java类支持复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。

什么是浅克隆和深克隆

通常情况下,一个类包含一些成员对象,在克隆对象时,根据其成员对象是否也克隆,克隆分为两种形式:深克隆和浅克隆。

浅克隆

在浅克隆中,被复制对象的所有普通成员变量都具有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅克隆仅仅复制所考虑的对象,而不复制它所引用的成员对象,也就是其中的成员对象并不复制。在浅克隆中,当对象被复制时它所包含的成员对象却没有被复制。
图片描述
图中obj1为原型对象,obj2为复制后的对象,containedObj1和containedObj2为成员对象。

深克隆

在深克隆中被复制对象的所有普通成员变量也都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过来的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要复制的对象所引用的对象都复制了一遍。在深克隆中,除了对象本身被复制外,对象包含的引用也被复制,也就是其中的成员对象也被复制。
图片描述
Java中clone()方法解析

Java语言提供的clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足:

对任何的对象x,都有x.clone() != x,即克隆对象与原对象不是同一个对象。
对任何对对象x,都有x.clone().getClass() == x.getClass(),即克隆对象与原对象的类型一样。
如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
为了获取对象的一份拷贝,我们可以利用Object类的clone()方法,具体步骤如下:

在派生类中覆盖基类的clone()方法,并声明为public。
在派生类的clone()方法中,调用super.clone()。
在派生类中实现Cloneable接口。
在Java中,通过覆盖Object类的clone()方法可以实现浅克隆,如果需要实现深克隆,可以通过序列化等方式实现。

序列化(Serialization)就是将对象写到流的过程,写到流中的对象是原对象的一个拷贝,而原对象仍然存在与内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,从而实现克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

Java提供的Cloneable接口和Serializable接口其代码都非常简单,它们是空接口,这种空接口也称为标示接口,标示接口中没有任何方法定义,其作用就是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆,是否支持序列化等。

实例

下面通过两个实例分别实现浅克隆和深克隆。

邮件浅克隆

1.实例说明

由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、主题、日期、附件等),某系统现需要提供一个邮件复制功能,对于已经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果要修改某部分内容,无须修改原始的邮件对象,只需要修改复制得到的邮件对象即可。在本实例中使用浅克隆实现邮件复制,复制邮件(E_mail)的同时不复制附件(Attachment)。

2.实例类图
图片描述
浅克隆类图

3.实例代码及解释

(1) 抽象原型类Object

package java.lang
    ...
    protected native Object clone() throws CloneNotSupportedExceptioni;
    ...

Object作为抽象原型类,在Java中,所有的类都是Object的子类,在Object中提供克隆方法clone(),用于创建一个原型对象,其clone()方法具体实现由JVM完成,用户使用时无须关心。

(2) 具体原型类Email(邮件类)

public class Email implements Cloneable {

    private Attachment attachment = null;

    public Email() {
        this.attachment = new Attachment();
    }

    public Object clone() {
        Email clone = null;
        try {
            clone = (Email) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return  clone;
    }

    public Attachment getAttachment() {
        return this.attachment;
    }

    public void display() {
        System.out.println("查看邮件");
    }

}

Email类是具体原型类,也是Object类的子类,在Java中,只有实现了Cloneable接口的类才能使用clone()方法来进行复制,因此Email类实现了Cloneable接口。在Email类中覆盖了Object的clone()方法,功过直接或间接调用Object的clone()方法返回一个克隆的原型对象。在Email类中定义了一个成员对象attachment,类型为Attachment。

4.辅助代码
(1) 附件类Attachment

public class Attachment {

    public void download() {
        System.out.println("下载附件");
    }

}

为了更好的说明深克隆和浅克隆的区别,在本实例中引入了附件类Attachment,邮件类Email与附件类是组合关联关系,在邮件类中定义一个附件类对象作为其成员对象。

(2) 客户端测试类Client

public class Client {

    public static void main(String[] args) {
        Email email, copyEmail;
        email = new Email();
        copyEmail = (Email) email.clone();

        System.out.println("email == copyEmail?");
        System.out.println(email == copyEmail);

        System.out.println("email.getAttachment == copyEmail.getAttachment?");
        System.out.println(email.getAttachment() == copyEmail.getAttachment());

    }

}

在Client客户端测试类中,比较原型对象和复制对象是否一致,并比较其成员对象attachment的引用是否一致。

5.结果分析

编译并运行客户端测试类,输出结果如下:

email == copyEmail?
false
email.getAttachment == copyEmail.getAttachment?
true

邮件深克隆

1.实例说明

使用深克隆实现邮件复制,即复制邮件的同时复制附件。

2.实例类图
图片描述
深克隆类图

3.实例代码与解释

import java.io.*;

public class Email implements Serializable {

    private Attachment attachment = null;

    public Email() {
        this.attachment = new Attachment();
    }

    public Object deepClone() throws IOException, ClassNotFoundException, OptionalDataException {

        // 将对象写入流中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);

        // 将对象从流中读出
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);

        return ois.readObject();

    }

    public Attachment getAttachment() {
        return this.attachment;
    }

    public void display() {
        System.out.println("查看邮件");
    }

}

Email作为具体原型类,由于实现的是深克隆,无须使用Object的clone()方法,因此无须实现Cloneable接口;通过序列化的方式实现深克隆,由于要将Email类型的对象写入流中,因此Email类需要实现Serializiable接口。

4.辅助代码

(1)附件类

import java.io.Serializable;

public class Attachment implements Serializable {

    public void download() {
        System.out.println("下载附件");
    }

}

作为Email类的对象成员,在深克隆中,Attachment类型的对象也将被写入流中,因此Attachment类也需要实现Serializiable接口。

(2)客户端测试类Client

public class Client {

    public static void main(String[] args) {
        Email email, copyEmail = null;
        email = new Email();

        try {
            copyEmail = (Email) email.deepClone();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("email == copyEmail?");
        System.out.println(email == copyEmail);

        System.out.println("email.getAttachment == copyEmail.getAttachment?");
        System.out.println(email.getAttachment() == copyEmail.getAttachment());

    }

}

5.结果及分析

编译运行客户端测试类,结果如下:

email == copyEmail?
false
email.getAttachment == copyEmail.getAttachment?
false

通过结果可以看出,表达式(email == copyEmail)结果为false,即通过复制得到的对象与原型对象的引用不一致,表达式(email.getAttachment() == copyEmail.getAttachment())结果为false,原型对象与克隆对象成员对象的引用不相同,说明其成员对象也复制了一份。

学习Java的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:492139965 我们一起学Java!

评论