浅克隆
- 如果原型对象的成员变量是值类型,将复制一份给克隆对象
- 如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,
即原型对象和克隆对象的成员变量指向相同的内存地址。
当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
解决多层克隆问题
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
public class Outer implements Serializable{ private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID public Inner inner; //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] public Outer myclone() { Outer outer = null; try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); outer = (Outer) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return outer; } }
Inner也必须实现Serializable,否则无法序列化:
public class Inner implements Serializable{ private static final long serialVersionUID = 872390113109L; //最好是显式声明ID public String name = ""; public Inner(String name) { this.name = name; } @Override public String toString() { return "Inner的name值为:" + name; } }
这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。
总结
实现对象克隆有两种方式:
实现Cloneable接口并重写Object类中的clone()方法
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆
基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。