我们都知道Java中所有的类都继承自Object类,Object类中实现了一个clone方法。按照常理来推断,它应该就是对类中的field进行copy一下然后返回就好了。但是Java的实现很奇葩,它首先检查当前类是不是实现了Cloneable接口,如果实现了,才进行clone这个操作,否则跑出CloneNotSupportedException,而且这个clone方法还是protected。因此,如果我们需要定义一个提供clone方法的类,需要干以下几件事:
- 实现Cloneable接口
- 将clone方法变成public,一个最基本的实现如下:
public class A implements Cloneable { private Object obj; @Override public A clone() { return (A)super.clone(); } }
以上的实现有几点问题:
- 首先就是deep copy的问题,如:
这显然不是我们想看到的,因为a2.obj的改变会导致a1.obj的改变,因此上述方法应该改为A b1 = new A(); A b2 = b1.clone(); System.out.println(b1.objA == b2.objA); // true
当然对于不同的filed我们需要不同的clone方法。public class A implements Cloneable { private Object obj; public A clone() throws CloneNotSupportedException { A a = (A) super.clone(); if (obj != null) a.obj = new Object(); return a; } }
- 其次,对于那些被设计于用于被继承类,我们应该这样实现:
也就是说,不要实现Cloneable接口,将clone方法保持protected的访问权限,这样就给了子类是否要实现clone的选择权。class A { Object objA; protected A clone() throws CloneNotSupportedException { A a = (A) super.clone(); if (objA != null) a.objA = new Object(); return a; } } class B extends A implements Cloneable { Object objB; public B clone() throws CloneNotSupportedException { B b = (B) super.clone(); if (objB != null) b.objB = new Object(); return b; } }
- 最后,我们在实现clone方法的时候应该遵循以下几个契约:
x.clone() != x //克隆出的object应该与被克隆的object独立 x.clone().getClass() == x.getClass() //克隆出来的对象应该与源对象同类 x.clone().equals(x) //克隆出的对象应该与源对象equal
Object类中的clone方法是用native方法实现的,而且那个Cloneable接口的使用方法也挺奇葩,它是作为一个标识而不是定义类的责任而存在的,因此书中建议如果我们要提供clone类似的功能,我们可以像C++中的那样提供copy构造函数,或者提供一个静态的copy方法,这样比较优雅和安全。这样做唯一的坏处就是要自己多写几行代码进行field到field的copy。