在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。
浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
浅clone:
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
示意如下图:
Object类中的native clone方法提供了浅clone的支持。可以实现Cloneable接口并调用Object的clone方法为一个类实现浅clone:
public static void main(String[] args) throws CloneNotSupportedException { Foo f1 = new Foo(1, new Bar(0)); Foo f2 = f1.clone(); System.out.println(f1); System.out.println(f2); f1.id = 2; f1.bar.setId(2); System.out.println(f1); System.out.println(f2); } public static class Foo implements Cloneable { private int id; private Bar bar; public Foo(int id, Bar bar) { this.id = id; this.bar = bar; } @Override public Foo clone() throws CloneNotSupportedException { return (Foo) super.clone(); } @Override public String toString() { return "Foo{" + "id=" + id + ", bar=" + bar + '}'; } } public static class Bar implements Cloneable { private int id; public Bar(int id) { this.id = id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Bar{" + "id=" + id + '}'; } }
程序的执行结果如下:
Foo{id=1, bar=Bar{id=0}}
Foo{id=1, bar=Bar{id=0}}
Foo{id=2, bar=Bar{id=2}}
Foo{id=1, bar=Bar{id=2}}
在示例程序中改变了f1的引用变量成员bar的值,f2的成员变量bar也受到了影响。不过改变了f1的直接类型成员id的值,f2却没有受影响。
可知:浅clone只能clone成员变量中直接类型的值,对于应用类型的值则是clone了引用对象的地址。
深clone:
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
示意如下图:
要让我们上面的程序支持深clone,需要对其做些调整:
首先需要给Bar类也添加clone方法的支持:
public static class Bar implements Cloneable { private int id; public Bar(int id) { this.id = id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Bar{" + "id=" + id + '}'; } @Override public Bar clone() throws CloneNotSupportedException { return (Bar) super.clone(); } }
而后修改Foo类的clone方法:
@Override public Foo clone() throws CloneNotSupportedException { Foo f = (Foo) super.clone(); f.bar = bar.clone(); return f; }
执行结果如下:
Foo{id=1, bar=Bar{id=0}} Foo{id=1, bar=Bar{id=0}} Foo{id=2, bar=Bar{id=2}} Foo{id=1, bar=Bar{id=0}}
此时f2不再受到f1的影响了。
此时再继续想一下,如果Bar中还有引用类型成员呢,如果Bar中的引用类型成员也有自己的引用类型成员,那么该怎么办呢?
挺好解决的事情,懒得解释了。