为什么要克隆对象

做开发很少用到克隆的。我能想得到的是用于调用方法时作为参数传递,为了保证方法调用前后对象的内部结构不被破坏,可以克隆一个对象作为参数传递。

使类具有克隆能力

有人可能注意到 Object 类中有一个 native 方法clone

protected native Object clone() throws CloneNotSupportedException;

访问修饰符是 protected,缺省的情况下Object 及其子类对象无法在别的类中访问 clone(),等同于所有类缺省没有克隆能力。

要具备克隆能力,必须实现 Cloneable 接口:

public interface Cloneable {
}

奇怪的是,这个接口是空的。然而不用想那么多,这只是个标记而已,同为标记接口的还有 java.io.Serializable 等。

Cloneable 存在有两个理由:

  1. 出于安全考虑,不想让所有的类都具有克隆能力,要求若想克隆必须实现此接口;
  2. 某个引用向上转型为基类后,你就不知道它是否能克隆,此时可以使用 instanceof 关键字检查该引用是否指向一个可克隆的对象。

要具备克隆能力,必须重写父类的 clone() 方法,同时将访问修饰符改为 public,必须使用 super.clone() 进行(浅)克隆。

super.clone() 做了什么

Object 中的 clone() 识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。

需要注意的是这里的复制是浅层复制(浅层克隆 shadow clone),下面举一个浅层复制的例子:

public class Student implements Cloneable {

    private int age;
    private String name;
    private Teacher teacher;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +
                '}';
    }

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

}
public class Teacher {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class CloneTest {

    public static void main(String[] args){
        Student s1 = new Student();
        s1.setAge(20);
        s1.setName("xiaoming");

        Teacher teacher = new Teacher();
        teacher.setName("wang");

        s1.setTeacher(teacher);
        System.out.println(s1);

        Student s2 = (Student) s1.clone();
        System.out.println(s2);

        s1.setAge(30);
        s1.setName("xiaohong");
        teacher.setName("li");
        System.out.println(s1);
        System.out.println(s2);
    }

}

输出为:

Student{age=20, name='xiaoming', teacher=wang}

Student{age=20, name='xiaoming', teacher=wang}

Student{age=30, name='xiaohong', teacher=li}

Student{age=20, name='xiaoming', teacher=li}

s1.setAge(30) 和 s1.setName("xiaohong") 都没有影响到克隆对象 s2。为什么? 这里说说我的理解。

基本数据类型或装箱基本数据类型在方法中作为参数传递的时候,都是传递的值的拷贝,所以单从它来讲已经做到了深层克隆。

String 类型你可以理解为是不可变的,一旦你做了改变(比如使用连接符做拼接),它也就变成另外一个对象了,不会影响到原对象,所以单从它来讲也做到了深层克隆。

teacher.setName("li") 影响到了克隆对象 s2,所以整个学生对象的克隆是浅层克隆。想要实现深层克隆,做以下修改:

public class Teacher implements Cloneable {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

Student的clone()修改为:

public Object clone() {
    try {
        Student student = (Student)super.clone();
        student.setTeacher((Teacher)student.getTeacher().clone());
        return student;
    } catch (Exception e) {
        return null;
    }
}

输出为:

Student{age=20, name='xiaoming', teacher=wang}

Student{age=20, name='xiaoming', teacher=wang}

Student{age=30, name='xiaohong', teacher=li}

Student{age=20, name='xiaoming', teacher=wang}

通过序列化进行深层拷贝

按照上面的深层克隆方法,如果类的结构不同,clone() 代码逻辑就不同,而且还可能涉及到大量的遍历和判断等复杂的操作。

嫌麻烦? 试试用序列化做深层拷贝吧。将对象进行序列化后再进行反序列化,其效果相当于克隆对象。

下面改改代码来证明这句话:

public class Student2 implements Serializable {

    private static final long serialVersionUID = -4890130009355939897L;

    private int age;
    private String name;
    private Teacher2 teacher;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher2 getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher2 teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +
                '}';
    }

}
public class Teacher2 implements Serializable {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
public class CloneTest2 {

    public static void main(String[] args) throws Exception{
        Student2 s2 = new Student2();
        s2.setAge(20);
        s2.setName("xiaoming");

        Teacher2 teacher2 = new Teacher2();
        teacher2.setName("wang");

        s2.setTeacher(teacher2);
        System.out.println(s2);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objOutputStream.writeObject(s2);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objInputStream = new ObjectInputStream(byteArrayInputStream);
        Student2 s22 = (Student2) objInputStream.readObject();
        System.out.println(s22.toString());

        s2.setAge(30);
        s2.setName("xiaohong");
        teacher2.setName("li");
        System.out.println(s2.toString());
        System.out.println(s22.toString());
    }

}



输出:


Student2{age=20, name='xiaoming', teacher=wang}

Student2{age=20, name='xiaoming', teacher=wang}

Student2{age=30, name='xiaohong', teacher=li}

Student2{age=20, name='xiaoming', teacher=wang}

几行序列化和反序列化代码,简单粗暴,适合绝大多数情况,再也不用为复杂的克隆逻辑而担忧了。