【Java进阶】Java浅克隆和深克隆

什么是浅克隆和深克隆

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

浅克隆

在浅克隆中,被复制对象的所有原始类型的变量(普通成员变量)都具有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。

换言之,浅克隆仅仅复制所考虑的对象(的原始类型的变量),而不复制它所引用的成员对象,也就是其中的成员对象并不复制。在浅克隆中,当对象被复制时它所包含的成员对象却没有被复制。

也就是,浅克隆得到的对象和原来的对象是两个不同的对象,它们的所有变量都在独立的对象(内存)中,其中原始类型的变量的值是相等的,而且引用类型的值也是相等的,即引用类型的变量引用的是同一个对象(这两个对象使用==比较时,返回true)。

深克隆

在深克隆中被复制对象的所有普通成员变量也都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过来的新对象,而不再是原有的那些被引用的对象。

换言之,深克隆把要复制的对象所引用的对象都复制了一遍。在深克隆中,除了对象本身被复制外,对象包含的引用也被复制,也就是其中的成员对象也被复制。

也就是,深克隆得到的对象和原来的是两个不同的对象,他们的所有变量都在独立的对象(内存,不同的地址)中,其中原始类型的变量的值是相等的,但是引用类型的值是不相等的,即引用类型的变量引用的是不相同的对象(这两个对象使用==比较时,返回false)。

实现浅克隆

Java语言中Object提供的clone()方法将对象复制了一份并返回给调用者。

clone方法

Object中的clone方法如下:

protected native Object clone() throws CloneNotSupportedException
  • 1

对于clone方法的说明

  1. 这是一个protected修饰的native方法,因此它的实现是取决于本地代码(不受程序员控制)。native方法的效率一般来说都是远高于java中的非native方法。

  2. Object中的clone方法是protected的,所以要使用clone就必须继承Object类(默认)。并且为了可以使其它类调用该方法,覆写克隆方法时必须将其作用域设置为public

  3. 克隆方法返回的是一个Object对象,所以必须要经过强制类型转换。

clone方法的部分注释如下

Creates and returns a copy of this object. The precise meaning of “copy” may depend on the class of the object. The general intent is that, for any object x, the expression: 
x.clone() != x will be true。

也即是,clone能够实现返回的对象和原来的对象是不相同的(并非是不相等),它们是两个独立的对象,在不同的内存地址上。

代码验证浅克隆

为了获取对象的一份拷贝,我们可以利用Object类的clone()方法,具体步骤如下:

  • 在派生类中实现Cloneable接口;
  • 在派生类中覆盖基类的clone()方法,并声明为public;
  • 在派生类的clone()方法中,调用super.clone()。
package javacore.test;

/**
 * description:
 *
 * @author liyazhou
 * @since 2017-08-09 16:27
 */

import java.io.Serializable;

/**
 * 自定义类 School
 */
class School implements Cloneable, Serializable{
    String name;
    public School(String name){
        this.name = name;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

/**
 * 自定义类Student
 */
class Student implements Cloneable, Serializable {
    String name;
    int age;
    School school;

    public Student(){}

    public Student(String name, int age, School school){
        this.name = name;
        this.age = age;
        this.school = school;
    }

    /**
     * @return 克隆出的对象
     * @throws CloneNotSupportedException,如果当前类没有实现Cloneable类,则抛出该异常
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class ShadowDeepClone {
    public static void main(String... args) throws CloneNotSupportedException {
        School school = new School("育才小学");
        Student student = new Student("小明", 10, school);

        // 克隆对象
        Student studentCopy = (Student)student.clone();

        // 验证两个对象是不是同一个对象
        System.out.println("student == studentCopy ? " + (student == studentCopy));
        // 原始类型(基本类型)的变量的值是一样的。
        System.out.println("student.age == studentCopy.age ? " + (student.age == studentCopy.age));
        // System.out.println("student == studentCopy ? " + (student.name == studentCopy.name));
        // 引用类型的成员变量的值是一样的,也就是引用同一个对象
        System.out.println("student.school == studentCopy.school ? " + (student.school == studentCopy.school));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

运行结果如下:

student == studentCopy ? false
student.age == studentCopy.age ? true  // 基本类型变量的值相等
student.school == studentCopy.school ? true  // 引用类型变量的值相等
  • 1
  • 2
  • 3

实现深克隆

可以使用序列实现深克隆。

序列化

序列化(Serialization)就是将对象写到流的过程,写到流中的对象是原对象的一个拷贝,而原对象仍然存在与内存中。

通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,从而实现克隆。

需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

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

代码验证深克隆

package javacore.test;

/**
 * description:
 *
 * @author liyazhou
 * @since 2017-08-09 16:27
 */

import org.junit.Test;

import java.io.*;

/**
 * 自定义类 School
 */
class School implements Cloneable, Serializable{
    String name;
    public School(String name){
        this.name = name;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

/**
 * 自定义类Student
 */
class Student implements Cloneable, Serializable {
    String name;
    int age;
    School school;

    public Student(){}

    public Student(String name, int age, School school){
        this.name = name;
        this.age = age;
        this.school = school;
    }

    /**
     * @return 克隆出的对象
     * @throws CloneNotSupportedException,如果当前类没有实现Cloneable类,则抛出该异常
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * 自定义的方法,通过序列化实现深克隆
     * @return
     */
    public  Object deepClone(){
        // if (src == null) return null;
        Object src = this;
        Object obj = null;
        try {
            // 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(src);  // 将该对象序列化到 baos 中

            // 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            obj = ois.readObject();

            // 应该在finally 代码块中关闭资源
            ois.close();
            bais.close();
            oos.close();
            baos.close();
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

public class ShadowDeepClone {
    School school = new School("育才小学");
    Student student = new Student("小明", 10, school);

    @Test
    public void deepClone(){
        Student studentCopy = (Student) student.deepClone();
        // 验证两个对象是不是同一个对象
        System.out.println("student == studentCopy ? " + (student == studentCopy));
        // 原始类型(基本类型)的变量的值是一样的。
        System.out.println("student.age == studentCopy.age ? " + (student.age == studentCopy.age));
        // System.out.println("student == studentCopy ? " + (student.name == studentCopy.name));
        // 引用类型的成员变量的值是一样的,也就是引用同一个对象
        System.out.println("student.school == studentCopy.school ? " + (student.school == studentCopy.school));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

运行结果如下:

student == studentCopy ? false
student.age == studentCopy.age ? true  // 基本类型的变量的值相等
student.school == studentCopy.school ? false  // 引用类型的变量的值不相等

猜你喜欢

转载自blog.csdn.net/c880420/article/details/80940188