先说说拷贝的具体含义,拷贝就是把对象复制一份一模一样的。浅拷贝是只复制了对象和值,如果浅拷贝中有其他引用类型的对象,那就不会被拷贝,而是和拷贝源使用同一个对象,指向同一地址,此时其中一个对象修改子对象中的字段变量了,会影响到另一个对象。深拷贝是把子对象一起执行复制操作,如果其中还有更多一级的引用类型对象,也会被递归复制,而不是和源对象指向同一地址。
下面为了帮助理解两种拷贝的方式,用代码来演示一下:
浅拷贝
先定义两个类:Person和Pet,Person需要实现Cloneable接口并重写clone方法
public class Person implements Cloneable {
private String name;
private int age;
private Pet pet;
public Person(String name, int age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
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 Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", pet=" + pet +
'}';
}
}
public class Pet {
private String name;
private int age;
public Pet(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 "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试代码如下:
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Pet pet = new Pet("Dog mi", 2);
Person person = new Person("Rosy", 24, pet);
// 打印对象的地址
System.out.println(System.identityHashCode(person));
System.out.println(System.identityHashCode(person.getPet()));
System.out.println(person);
System.out.println("----------------------------------");
Person clonePerson = person.clone();
// 打印对象的地址,判断和原来的对象是否是同一个
System.out.println(System.identityHashCode(clonePerson));
System.out.println(System.identityHashCode(clonePerson.getPet()));
System.out.println(clonePerson);
System.out.println("***********************************");
// 下面演示浅克隆出现的问题:此时想把克隆出来的对象的宠物修改名字为Cat uu
clonePerson.getPet().setName("Cat uu");
// 打印源对象和克隆后的对象查看效果
System.out.println(person);
System.out.println(clonePerson);
}
}
效果如下:
1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
2093176254
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}
从测试代码中可以看到,Person对象的确被克隆了一份新的,但是它的引用对象pet仍然和原来的pet地址相同。最直观的就是,我们修改克隆出来的对象的宠物名称时,源对象的宠物名称也被修改了。这个问题在一些场合是致命的问题,会导致程序出现很严重的问题。因为你可能并不想修改某一个对象,但是修改另一个克隆对象的时候同时也导致该对象被修改。
所以,此时我们就需要使用到深克隆,把子对象也实现克隆,而不是指向原对象的地址。
深克隆
实现深克隆的方法一:手动实现子对象克隆
下面先修改一下Person和Pet类。Person需要在clone方法里调用Pet的clone方法,Pet需要实现Cloneable接口。
public class Person implements Cloneable {
private String name;
private int age;
private Pet pet;
public Person(String name, int age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
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 Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
protected Person clone() throws CloneNotSupportedException {
Person cloneObj = (Person) super.clone();
// 克隆一个新的Pet对象,设定到新克隆的Person对象上
cloneObj.setPet(this.getPet().clone());
return cloneObj;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", pet=" + pet +
'}';
}
}
public class Pet implements Cloneable{
private String name;
private int age;
public Pet(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
protected Pet clone() throws CloneNotSupportedException {
return (Pet) super.clone();
}
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试代码不变:
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Pet pet = new Pet("Dog mi", 2);
Person person = new Person("Rosy", 24, pet);
// 打印对象的地址
System.out.println(System.identityHashCode(person));
System.out.println(System.identityHashCode(person.getPet()));
System.out.println(person);
System.out.println("----------------------------------");
Person clonePerson = person.clone();
// 打印对象的地址,判断和原来的对象是否是同一个
System.out.println(System.identityHashCode(clonePerson));
System.out.println(System.identityHashCode(clonePerson.getPet()));
System.out.println(clonePerson);
System.out.println("***********************************");
// 下面尝试把克隆出来的对象的宠物修改名字为Cat uu
clonePerson.getPet().setName("Cat uu");
// 打印源对象和克隆后的对象查看效果
System.out.println(person);
System.out.println(clonePerson);
}
}
执行效果如下:
1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
2093176254
1854731462
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}
从地址上来看,新Person和原Person已经克隆成功了,这点和浅克隆一致。但是浅克隆没有实现的Pet克隆在这里被实现了,而且修改名字为Cat uu的时候也并不会影响原来的对象,这就是深克隆,把对象全部克隆而并不是某些部分保留原来的引用地址。
实现深克隆的方式二:序列化和反序列化
下面使用的Person和Pet和浅克隆时的差不多,但是需要多实现一个接口:Serializable 。只有实现这个接口的对象才能够序列化,子对象是引用类型的也需要实现。
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private Pet pet;
public Person(String name, int age, Pet pet) {
this.name = name;
this.age = age;
this.pet = pet;
}
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 Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", pet=" + pet +
'}';
}
}
public class Pet implements Serializable {
private String name;
private int age;
public Pet(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 "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
使用序列化和反序列化克隆对象:
import java.io.*;
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Pet pet = new Pet("Dog mi", 2);
Person person = new Person("Rosy", 24, pet);
// 打印对象的地址
System.out.println(System.identityHashCode(person));
System.out.println(System.identityHashCode(person.getPet()));
System.out.println(person);
System.out.println("----------------------------------");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("at0914a.txt")));){
// 把person对象写入at0914.txt文件里
oos.writeObject(person);
} catch (IOException e) {
throw new RuntimeException(e);
}
Person clonePerson = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("at0914a.txt")));){
// 把写入at0914.txt文件里的文本给反序列化成Person类型的对象
clonePerson = (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
// 打印对象的地址,判断和原来的对象是否是同一个
System.out.println(System.identityHashCode(clonePerson));
System.out.println(System.identityHashCode(clonePerson.getPet()));
System.out.println(clonePerson);
System.out.println("***********************************");
// 下面尝试把克隆出来的对象的宠物修改名字为Cat uu
clonePerson.getPet().setName("Cat uu");
// 打印源对象和克隆后的对象查看效果
System.out.println(person);
System.out.println(clonePerson);
}
}
控制台输出效果如下:
1096979270
1078694789
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
----------------------------------
1490180672
460332449
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
***********************************
Person{name='Rosy', age=24, pet=Pet{name='Dog mi', age=2}}
Person{name='Rosy', age=24, pet=Pet{name='Cat uu', age=2}}
从地址和输出的对象属性来看,已经是成功设置深克隆了。
我们在使用克隆的时候,要尽量避免使用浅克隆,而是使用深克隆。除非你需要有一些特殊需求,比如说两个人养同一条宠物,那使用浅克隆也未尝不可。