Java:浅克隆(shallow clone)与深克隆(deep clone)
Summary
浅克隆与深克隆对于JavaSE来说,是个难度系数比较低的概念,但不应该轻视它。
假设一个场景:对于某个list,代码里并没有任何对其的直接操作,但里面的元素的属性却被改变了,这可能就涉及到这个概念。
Description
浅克隆指仅copy对象位于栈内存中的引用(reference)。copy后,新旧两个引用指向同一个堆内存对象(即同一内存区域),但是堆内存中实际的对象copy前后均只有一个。使用"==" operator比较二者的地址会返回true。(不同引用,同一对象)
深克隆指则会copy一个新的对象并返回相应引用,即开辟了新的堆内存空间,因此使用“==” operator来比较两者的地址时会返回false。(不同引用,不同对象)
浅克隆(shallow clone)
- clone对象是实例对象时,使用“=”操作符进行浅克隆。
- clone对象是对象数组的元素时,使用
System.arraycoppy()
进行浅克隆。(你非得要用"=" foreach地clone也没人拦着)
jdk中显式定义的clone操作基本上都使用:
1 System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
例如ArrayList中的clone()、Arrays.copyOf()等对具体数组的clone其实底层都是调用该方法。
1 package com.scv.test.clone; 2 3 public class ShallowCloneTest { 4 5 public static void main(String[] args) throws Exception { 6 Zerg z0 = new Zerg(); 7 Zerg z1 = z0; 8 System.out.println("0. " + (z0 == z1));//"="操作符用于对象的浅克隆 9 10 Zerg[] array0 = new Zerg[]{new Zerg(), new Zerg()}; 11 Zerg[] array1 = new Zerg[array0.length]; 12 System.arraycopy(array0, 0, array1, 0, array0.length);//System.arraycopy()用于数组的浅克隆 13 System.out.println("1. " + (array0 == array1)); 14 for(int i = 0; i < array0.length; i++){ 15 System.out.println("Comparing element of array:" + (array0[i] == array1[i])); 16 } 17 } 18 19 } 20 21 class Zerg{ 22 23 } 24 25 /* Output: 26 0. true 27 1. false 28 Comparing element of array:true 29 Comparing element of array:true 30 */
深克隆(deep clone)
jdk中并没有显式定义深克隆,或者说并没有直接提供工具类来进行。要让你的自定义类支持深克隆,必须具备两个条件:
- implements Cloneable interface.
- override clone() defined in java.lang.Object.
如果不实现Cloneable而直接override Object的clone(),则会抛出CloneNotSupportedException。
1 package com.scv.test.clone; 2 3 public class DeepCloneTest { 4 5 public static void main(String[] args) throws Exception { 6 CloneableZerg z0 = new CloneableZerg(); 7 CloneableZerg z1 = z0.clone(); 8 9 System.out.println("0. " + (z0 == z1)); 10 } 11 12 } 13 14 class CloneableZerg implements Cloneable{ 15 16 @Override 17 public CloneableZerg clone() throws CloneNotSupportedException{ 18 return (CloneableZerg)super.clone(); 19 } 20 } 21 22 /* Output: 23 0. false 24 */
实际上,你可以自定义哪些成员变量(field)允许clone,哪些不允许(有点transient的感觉?)。
jdk中的实现:ArrayList中的浅克隆与深克隆
1 package com.scv.test.clone; 2 3 import java.util.ArrayList; 4 5 public class ArrayListCloneTest { 6 7 public static void main(String[] args) throws Exception { 8 CloneTarget t = new CloneTarget(); 9 10 ArrayList<CloneTarget> list0 = new ArrayList<CloneTarget>(1); 11 list0.add(t); 12 ArrayList<CloneTarget> list1 = (ArrayList<CloneTarget>) list0.clone(); 13 list0.get(0).setFieldA(20); 14 15 System.out.println("0. " + (list0 == list1)); 16 System.out.println("1. " + (list0.get(0) == list1.get(0))); 17 System.out.println("2. " + list1.get(0).getFieldA()); 18 } 19 20 } 21 22 class CloneTarget implements Cloneable{ 23 24 private int fieldA = 10; 25 26 @Override 27 public CloneTarget clone() throws CloneNotSupportedException{ 28 return (CloneTarget)super.clone(); 29 } 30 31 public void setFieldA(int a){ 32 fieldA = a; 33 } 34 35 public int getFieldA(){ 36 return fieldA; 37 } 38 } 39 40 /* 41 * Output: 42 * 0. false 43 * 1. true 44 * 2. 20 45 */
操作说明:
- 创建一个ArrayList对象list0
- list0中加入一个对象t
- 克隆list0对象为list1
- 再修改list0中元素(即t)的属性
结果说明:
- ArrayList实现了Cloneable接口,arraylist.clone()为深克隆,故list0与list1分别指向不同内存区域。
- ArrayList对象的clone()对于内部数组的元素仅为浅克隆,故list0中的元素(t)与list1中的元素为同一个,对list0元素的修改将影响到list1的元素。