为什么要使用对象克隆 首先需要说明的是,直接用=
符号来复制,只是复制了原有对象的引用,对新对象的修改同样也会作用原对象上。
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class A { private String name; public A (String name) { this .name = name; } public String getName () { return name; } public void setName (String name) { this .name = name; } } public class CloneTest { public static void main (String[] args) { A a = new A("张三" ); A b = a; System.out.println(a.getName()); b.setName("李四" ); System.out.println(a.getName()); System.out.println(b.getName()); } }
如上,通过等于符号来赋值只是将对象的引用地址复制了一份。所以想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了,Java 语言中克隆针对的是类的实例。
实现 对象克隆的实现一共有三种方式,一种浅拷贝,两种深度克隆。
实现 Cloneable 接口并重写 Object 类中的 clone()方法(浅拷贝) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 class Person implements Cloneable { private String name; private int age; private Car car; public Person (String name, int age, Car car) { this .name = name; this .age = age; this .car = car; } 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; } public Car getCar () { return car; } public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]" ; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } } class Car implements Cloneable { private String brand; private int maxSpeed; public Car (String brand, int maxSpeed) { this .brand = brand; this .maxSpeed = maxSpeed; } public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public int getMaxSpeed () { return maxSpeed; } public void setMaxSpeed (int maxSpeed) { this .maxSpeed = maxSpeed; } @Override public String toString () { return "Car [brand=" + brand + ", maxSpeed = " + maxSpeed + "]" ; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } } public class CloneTest { public static void main (String[] args) throws CloneNotSupportedException { Car c1 = new Car("bc" , 100 ); Person p1 = new Person("a" , 1 , c1); Person p2 = (Person) p1.clone(); System.out.println(p2); p2.setName("b" ); p2.getCar().setBrand("bm" ); System.out.println(p2); System.out.println(p1); } }
为什么是浅克隆?当对象的属性为其他对象时,会出现一个问题:只复制了属性的引用,而没有拷贝属性的值。
clone 对象时也 clone 属性,实现深度克隆 缺点是自定义引用过多时,将增加代码复杂度。
clone 方法示例如下,其余跟浅克隆一样:
1 2 3 4 5 6 @Override protected Object clone () throws CloneNotSupportedException { Person person = (Person) super .clone(); person.setCar((Car) person.getCar().clone()); return person; }
实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 class MyUtil { private MyUtil () { throw new AssertionError(); } @SuppressWarnings("unchecked") public static <T extends Serializable> T clone (T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bout); oos.writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bin); return (T) ois.readObject(); } } class Person implements Serializable { private String name; private int age; private Car car; public Person (String name, int age, Car car) { this .name = name; this .age = age; this .car = car; } 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; } public Car getCar () { return car; } public void setCar (Car car) { this .car = car; } @Override public String toString () { return "Person [name=" + name + ", age=" + age + ", car=" + car + "]" ; } } class Car implements Serializable { private String brand; private int maxSpeed; public Car (String brand, int maxSpeed) { this .brand = brand; this .maxSpeed = maxSpeed; } public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public int getMaxSpeed () { return maxSpeed; } public void setMaxSpeed (int maxSpeed) { this .maxSpeed = maxSpeed; } @Override public String toString () { return "Car [brand=" + brand + ", maxSpeed = " + maxSpeed + "]" ; } } public class CloneTest { public static void main (String[] args) throws Exception { Car c1 = new Car("bc" , 100 ); Person p1 = new Person("a" , 1 , c1); Person p2 = MyUtil.clone(p1); System.out.println(p2); p2.setName("b" ); p2.getCar().setBrand("bm" ); System.out.println(p2); System.out.println(p1); } }
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用 Object 类的 clone 方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
浅拷贝和深拷贝的区别
浅拷贝对于对象属性来说只是复制了对象属性的引用地址,两个不同对象的同一属性依然指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝(例:assign ())
深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝(例:JSON.parse () 和 JSON.stringify (),但是此方法无法复制函数类型)