Cloneable接口的通用约定很弱,新建对象,然后逐域浅拷贝就能满足。所以Object的clone()方法实现的就是浅拷贝。但浅拷贝只对基本型和不可变对象有效。因为基本型没有引用,直接复制值。不可变对象只读不写也不受影响,而且来就是要控制实例,复制引用正合适。但可变对象如果是浅拷贝就不行。还需要另外重新调整,比如递归调用可变对象的clone()方法。这章第二个关键点,就是对Cloneable接口的继承问题。惯用法是,尽量让Object#clone()方法替我们工作。因此当一个类需要实现clone()方法,理想的情况就是它的所有超类的clone()方法都递归调用了super.clone()方法,最终成功追溯到Object#clone()方法。
Cloneable接口的通用约定非常弱:逐域浅拷贝就能满足
Cloneable的通用约定有三:
- x.clone() != x为- true。
- x.clone().getClass() == x.getClass()为- true。 但这不是绝对的要求。
- x.clone().equals(x)为- true。 但这也不是绝对的要求。
想像下面这个ABC类,包含了A,B,C三个类型的域。copy()方法创建一个新对象,然后逐域拷贝引用,这是一个标准的浅拷贝,但它却满足Cloneable接口的通用约定。
public class ABC {
    A a;
    B b;
    C c;
    public ABC(A a, B b, C c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public ABC copy(ABC o) { // 这个copy完全执行浅拷贝,但返回对象满足Cloneable接口的约定
        return new ABC(o.a, o.b, o.c);
    }
}
Object类的clone()方法的行为
Object类源码中的注释部分如下:
First, if the class of this object does not implement the interface {@code Cloneable}, then a code CloneNotSupportedException is thrown. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation. Note that all arrays are considered to implement the interface {@code Cloneable}
总结起来就是三点:
- 类如果没有实现Clonable接口,调用clone()方法会抛出CloneNotSupportedException异常。
- 实现了Clonable接口的类,clone()方法需要执行一个浅拷贝。
- 任何类型的数组都默认实现了Clonable接口。
尽可能接近深拷贝的clone()方法惯用法
clone()方法只做 浅拷贝 就可以满足Cloneable接口的约定。 浅拷贝 都是复制对象,而不是内容。
- 基本型 没有引用,所以不存在 浅拷贝的情况,都是深拷贝。
- 不可变对象 做深拷贝是不应该发生的事,比如String和Enum,尤其是Enum应该绝对禁止拷贝。
- 可变的对象 是可以深拷贝的。这需要一个类域中的可变对象都递归实现了Cloneable接口。
下面的代码展示了一个类实现Cloneable接口的惯用法。
- 对这个类以及这个类的所有超类递归实现Cloneable接口
- 对这个类中所有的可变对象域,递归实现Cloneable接口
这样实现的结果,也只能说尽可能地接近深拷贝。因为其中的不可变域还是浅拷贝。但这样的浅拷贝并不影响实际的使用。
public class Person implements Cloneable {
    private final String name; // String is not Cloneable
    private final int age;
    private final Sex sex; // Enum is not Cloneable
    private final PhoneNumber telephone;
    // ... some other code here
    @Override
    public Object clone() {
        try {
            return (Person)super.clone();
        } catch (CloneNotSupportedException e) { // cannot happen
            return new RuntimeException(e);
        }
    }
}
public class PhoneNumber implements Cloneable {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;
    @Override
    public Object clone() {
        try {
            return (PhoneNumber)super.clone();
        } catch (CloneNotSupportedException e) { // cannot happen
            return new RuntimeException(e);
        }
    }
}
第二条惯用法:尽量让Object#clone()替我们工作,递归调用超类super.clone()方法,以保持一条完整的自动构造器调用链
当一个类要实现一个行为良好的clone()方法,如果它的所有超类都递归调用了super.clone()方法,并最终追溯到最终超级父类Object的clone()方法,那么Object的clone()方法总能复制并返回一个正确的类型。因此,这个类也可以调用super.clone()方法就能获得一个正确的拷贝对象。
但如果中间哪一个类没有调用super.clone()方法,而是使用了自己的构造器返回克隆的实例,那么它的所有子类就都不能获得Object的clone()方法提供的良好服务。而Object的clone()方法是行为良好的clone()方法的有力保障。所以,尽量保持自动向上构造器调用链的畅通。
具体参考下面这个例子,Person,Employee,Manager三个类层层继承。Employee比Person多了一个表示职位的域position,Manager又比Employee多了一个表示持有公司股票份额的域stock。这三个类的clone()方法异常简单,都只是向上转达了对super.clone()的调用。而最后实际执行工作的Object#clone()能够识别出调用对象mg运行时的实际类型,并且成功拷贝了Manager的所有四个域。将任务交给Object#clone()来做,事情就变得简单许多。
package com.ciaoshen.effectivejava.chapter3;
public class TestConstructor {
    private static class Person implements Cloneable {
        protected final String name;
        protected short age;
        public Person(String name, int age) {
            this.name = name;
            this.age = (short)age;
        }
        public String toString() {
            return "Person[" + name + ", " + age + "]";
        }
        public Person clone() {
            try {
                return (Person)super.clone(); // 最后实际执行任务的是Object#clone()方法,它识别出实际类型为Manager,然后逐域拷贝。
            } catch(CloneNotSupportedException e) {
                throw new RuntimeException(e); // never happen
            }
        }
    }
    private static class Employee extends Person implements Cloneable {
        protected String position;
        public Employee(String name, int age, String position) {
            super(name,age);
            this.position = position;
        }
        public String toString() {
            return "Employee[" + name + ", " + age + ", " + position + "]";
        }
        public Employee clone() {
            return (Employee)super.clone();
        }
    }
    private static class Manager extends Employee implements Cloneable {
        protected int stock;
        public Manager(String name, int age, String position, int stock) {
            super(name,age,position);
            this.stock = stock;
        }
        public String toString() {
            return "Manager[" + name + ", " + age + ", " + position + ", " + stock + "]";
        }
        public Manager clone() {
            return (Manager)super.clone();
        }
    }
    public static void main(String[] args) {
        Manager mg = new Manager("Ronald", 30, "Chef de project", 10000);
        Manager mgCopy = mg.clone(); // 最终追溯到Object#clone()
        System.out.println(mgCopy);
    }
}
提供一个拷贝工厂是个好做法
和静态工厂一样,为复杂对象的拷贝提供一个拷贝工厂也可以让事情变得更简单明了。
最后的忠告
- 其他接口都不应该扩展Cloneable接口。
- 为了继承而实现的类也不应该实现Cloneable接口。
- 最好就是根本不要去用Cloneable接口和clone()方法。