深克隆和浅克隆
1.前提
要实现java对象的克隆必须要实现Cloneable接口,但是这个接口并不包含方法只是一个标志性接口。具体看该接口的注释:
A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.(实现这个接口只是标识这个类对象属性使用Oobject的clone()方法进行copy是合法的。)
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown. (在没有实现这个接口的类对象上调用Object的clone方法会抛出CloneNotSupportedException)
* <p>
* By convention, classes that implement this interface should override
* {@code Object.clone} (which is protected) with a public method.(通常实现这个接口的类应当重写Object的clone方法(该方法被protected修饰)为public)
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
由注释我们可以知道要实现对象的克隆需要以下步骤
1.实现Cloneable接口 表示这个类可以被克隆
2.重写clone()方法且权限修饰符为public
clone方法复制的对象和原对象 需保证
x.clone() != x 始终为true
x.clone().getClass() == x.getClass() 始终为true
但是 x.clone().equals(x) 并不需要保证。
2.深克隆(deep copy)和浅克隆(shallow copy)
Note that all arrays
are considered to implement the interface {@code Cloneable} and that
the return type of the {@code clone} method of an array type {@code T[]}
is {@code T[]} where T is any reference or primitive type.
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.
上面这段是Object的clone方法的注释,从上面我们可以看到。
1.不管基本类型还是饮用类型克隆之后类型还是原来的类型
2. 在克隆的时候,clone方法会创建一个该类的实例并且为每个克隆对象的字段从原对象对应字段取值进行初始化。3.这些字段的内容本身没有被克隆,这个方法这种表现称为浅克隆。从上面我们可以得出深克隆和浅克隆的区别,但这句话有点语焉不详。要怎么理解字段内容本身没有被克隆呢?
浅克隆代码:
实体类:PersonSource
public class PersonSource implements Cloneable { private int age; private String name; private Relative relative; public PersonSource(){}; public PersonSource(int age,String name,Relative relative) { this.age = age; this.name = name; this.relative = relative; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Relative getRelative() { return relative; } public void setRelative(Relative relative) { this.relative = relative; } @Override public String toString() { return "PersonSource:{" + "age:" + age + ", name:'" + name + '\'' + ", relative:" + relative.toString() + '}'; } // 浅克隆直接调用重写父类的clone方法 @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
作为引用类型类:Relative 为便于后面深克隆的使用,我们先给这个类实现Cloneable接口,并重写clone方法
public class Relative implements Cloneable { private String name; private int age; public Relative(){}; public Relative(String name,int age){ this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Relative{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override protected Object clone() throws CloneNotSupportedException { // 如果relative有引用类型的属性则也要引用类型属性也要重写clone方法 return super.clone(); } }
测试类:
public class CopyTest { public static void main(String[] args) throws CloneNotSupportedException { // 引用类型 Relative relative = new Relative("relative",12); PersonSource personSource = new PersonSource(34,"personSource",relative); PersonSource copy = (PersonSource) personSource.clone(); System.out.println("age:" + copy.getAge()); // 34 System.out.println("name:" + copy.getName()); // personSource System.out.println("relative:" + copy.getRelative().toString()); // Relative{name='relative', age=12} System.out.println(copy == personSource); // false 的确创建了一个新的对象 System.out.println(copy.getRelative() == personSource.getRelative()); // true 两个引用指向同一个地址,没有新对象产生 copy.setAge(333); copy.setName( "test"); copy.getRelative().setAge(567); copy.getRelative().setName("copy"); System.out.println("原对象:" + personSource.toString()); // {age:34, name:'personSource', // relative:Relative{name='copy', age=567}} System.out.println("copy后:" + copy.toString()); // {age:333, name:'test', // relative:Relative{name='copy', age=567}} } }
可以看到 基本类型属性在克隆后不会和原对象有关联,但是引用类型在改变克隆对象的引用属性后,原对象也发生了变化。
因此浅克隆会创建一个对象,且基本类型的属性每个对象独有,引用类型属性和原对象共有。
深克隆代码:
修改 PersonSource 的clone方法
1 public class PersonSource implements Cloneable { 2 3 private int age; 4 private String name; 5 private Relative relative; 6 7 public PersonSource(){}; 8 9 public PersonSource(int age,String name,Relative relative) { 10 this.age = age; 11 this.name = name; 12 this.relative = relative; 13 } 14 15 public int getAge() { 16 return age; 17 } 18 19 public void setAge(int age) { 20 this.age = age; 21 } 22 23 public String getName() { 24 return name; 25 } 26 27 public void setName(String name) { 28 this.name = name; 29 } 30 31 public Relative getRelative() { 32 return relative; 33 } 34 35 public void setRelative(Relative relative) { 36 this.relative = relative; 37 } 38 39 @Override 40 public String toString() { 41 return "PersonSource:{" + 42 "age:" + age + 43 ", name:'" + name + '\'' + 44 ", relative:" + relative.toString() + 45 '}'; 46 } 47 48 49 50 @Override 51 public Object clone() throws CloneNotSupportedException { 52 PersonSource personSource = (PersonSource) super.clone(); 53 personSource.relative = (Relative) relative.clone(); 54 55 return personSource; 56 } 57 }
测试后的结果:
public class CopyTest { public static void main(String[] args) throws CloneNotSupportedException { // 引用类型 Relative relative = new Relative("relative",12); PersonSource personSource = new PersonSource(34,"personSource",relative); PersonSource copy = (PersonSource) personSource.clone(); System.out.println("age:" + copy.getAge()); // 34 System.out.println("name:" + copy.getName()); // personSource System.out.println("relative:" + copy.getRelative().toString()); // Relative{name='relative', age=12} System.out.println(copy == personSource); // false 的确创建了一个新的对象 System.out.println(copy.getRelative() == personSource.getRelative()); // false 地址不同,有新对象产生 copy.setAge(333); copy.setName( "test"); copy.getRelative().setAge(567); copy.getRelative().setName("copy"); System.out.println("原对象:" + personSource.toString()); // {age:34, name:'personSource', // relative:Relative{name='relative', age=12}} System.out.println("copy后:" + copy.toString()); // {age:333, name:'test', // relative:Relative{name='copy', age=567}} } }
根据结果可以知道 深克隆后引用类型的属性在堆中创建了新的对象。
因此注释里边的 浅克隆时 字段内容本身不会被克隆,指的应该是引用类型的属性在浅克隆时只会复制一份栈中的地址,并不会复制堆中的对象。
深克隆与浅克隆的区别:
1. 在代码实现上 如果只有基本类型属性则可以重写直接调用父类clone方法;如果一个类A有引用类型的属性B且想实现深克隆 则属性B必须也实现Cloneable接口且重写clone方法,并在A类重写clone方法时调用B类重写的clone方法。
2. 在实现效果上 如果都是基本类型(字符串因为常量池可视为基本类型)则深克隆浅克隆效果一致;如果有引用类型的属性,浅克隆只会复制引用类型属性的地址,深克隆会复制产生新的对象。