Java的克隆
-
在 Object 基类中,有一个clone()方法,克隆对象是原对象的拷贝。
- clone()方法是 Object 类的,并不是 Cloneable 接口的,Cloneable 只是一个标记接口,如果没有实现 Cloneable 接口,那么调用clone()方法就会爆出 CloneNotSupportedException 异常。
- Object 类中的 clone()方法是 protected 修饰的,所以我们要在子类中重写这个方法,并且设置成 public,才能在子类外部进行访问。
- clone() 方法是 native 方法,native 方法的效率远高于非 native 方法,因此如果我们需要拷贝一个对象,建议使用 clone(),而不是 new。
- 返回值是一个Object对象,因此通过clone方法克隆一个对象,需要强制转换。
protected native Object clone() throws CloneNotSupportedException;
-
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于
是否支持引用类型的成员变量的复制
,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。
浅克隆
-
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。也就是说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
-
实现步骤:
- 被克隆的类
实现Cloneable接口
覆写clone()方法
public class ShallowCloneTest { public static void main(String[] args) throws CloneNotSupportedException { Food food = new Food("梅菜扣肉", 15); Person person = new Person("zs", 16, food); Person person2 = (Person) person.clone(); System.out.println(person.getName() + "," + person.getAge()); System.out.println(person2.getName() + "," + person2.getAge()); System.out.println(person.getFood()); System.out.println(person2.getFood()); } } class Person implements Cloneable { private String name; private Integer age; private Food food; public Person(String name, Integer age, Food food) { this.name = name; this.age = age; this.food = food; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Food getFood() { return food; } public void setFood(Food food) { this.food = food; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } class Food { private String name; private Integer price; public Food(String name, Integer price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } }
- 被克隆的类
-
输出结果:可以看到 person2 复制了 person 的值类型成员变量 name 和 age,但是 person2 对 person 中的引用类型成员变量 food 只是进行了地址的复制,所有两个对象的food属性都是指向同一个地址。
.
深克隆
-
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。也就是说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
实现深克隆方式一:实现Cloneable接口
-
可以通过覆盖 Object 类的 clone() 方法并进行递归调用实现
public class DeepCloneTest01 { public static void main(String[] args) throws CloneNotSupportedException { Food food = new Food("梅菜扣肉", 15); Person person = new Person("zs", 16, food); Person person2 = (Person) person.clone(); System.out.println(person.getName() + "," + person.getAge()); System.out.println(person2.getName() + "," + person2.getAge()); System.out.println(person.getFood()); System.out.println(person2.getFood()); } } class Person implements Cloneable { private String name; private Integer age; private Food food; public Person(String name, Integer age, Food food) { this.name = name; this.age = age; this.food = food; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Food getFood() { return food; } public void setFood(Food food) { this.food = food; } @Override public Object clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); person.food = (Food) this.food.clone(); return person; } } class Food implements Cloneable{ private String name; private Integer price; public Food(String name, Integer price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
-
输出结果:可以看到,深克隆不仅对值类型成员变量进行了拷贝,对引用类型的成员变量也拷贝了一份,而不是简单的地址复制。
.
实现深克隆方式二:实现Serializable接口
-
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone()方法就会很麻烦。这时我们可以通过
实现Serializable接口
,用序列化的方式来实现对象的深克隆。public class Test05 { public static void main(String[] args) { Food01 food01 = new Food01("梅菜扣肉", 15); Person01 person = new Person01("zs", 16, food01); Person01 person2 = (Person01) person.myclone(); System.out.println(person.getName() + "," + person.getAge()); System.out.println(person2.getName() + "," + person2.getAge()); System.out.println(person.getFood()); System.out.println(person2.getFood()); } } class Person01 implements Serializable { //显式声明序列化ID private static final long serialVersionUID = 369285298572941L; private String name; private Integer age; private Food01 food; public Person01 myclone() { Person01 person01 = null; //写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝 try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); person01 = (Person01) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return person01; } public Person01(String name, Integer age, Food01 food) { this.name = name; this.age = age; this.food = food; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Food01 getFood() { return food; } public void setFood(Food01 food) { this.food = food; } } class Food01 implements Serializable{ private String name; private Integer price; public Food01(String name, Integer price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } }
-
输出结果:
. -
注意:
基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。