通常我们想复制一个对象,可以自己new出一个对象,并对相应的属性存入数据以实现克隆的目的。可是当属性较多时,这样的方式会十分麻烦。然而Java早已考虑好一切,我们知道,所有的对象都有一个共同的父类Object,而Object类中已经定义好了protected修饰的clone()方法,今天我们从这个方法开始,讲述Java的浅克隆与深克隆。
浅克隆与深克隆的区别
**浅克隆:**复制对象时仅仅复制对象本身,包括基本属性,但该对象的属性引用其他对象时,该引用对象不会被复制,即拷贝出来的对象与被拷贝出来的对象中的属性引用的对象是同一个。
**深克隆:**复制对象本身的同时,也复制对象包含的引用指向的对象,即修改被克隆对象的任何属性都不会影响到克隆出来的对象。
克隆示例
Java的Object类提供的clone()方法能够完成浅克隆操作,但因其提供的方法被protected所修饰,因此我们要使用如下操作来使一个类能够实现克隆:
- 对象的类需要实现Cloneable接口
- 重写Object类中的clone()方法
- 根据重写的clone()方法得到想要的克隆结果,例如浅克隆与深克隆。
浅克隆
import java.util.Objects;
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student("小明", 18, new School("一号小学", 10000));
Student student2 = student1.clone();
System.out.println(student1 == student2); // false
System.out.println(student1.getName() == student2.getName()); // true
System.out.println(Objects.equals(student1.getName(), student2.getName())); // true
System.out.println(student1.getAge() == student2.getAge()); // true
System.out.println(student1.getStudentSchool() == student2.getStudentSchool()); // true
student1.getStudentSchool().setSchoolName("二号中学");
student1.getStudentSchool().setPostcode(999999);
System.out.println(student2.getStudentSchool().getSchoolName()); // 二号中学
System.out.println(student2.getStudentSchool().getPostcode()); // 999999
student1.setAge(26);
System.out.println(student2.getAge()); // 18
student1.setName("小华");
System.out.println(student2.getName()); // 小明
}
}
class Student implements Cloneable {
private String name;
private int age;
private School studentSchool;
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 School getStudentSchool() {
return studentSchool;
}
public void setStudentSchool(School studentSchool) {
this.studentSchool = studentSchool;
}
public Student(String name, int age, School studentSchool) {
this.name = name;
this.age = age;
this.studentSchool = studentSchool;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone(); //调用父类的clone方法
}
}
class School {
private String schoolName;
private int postcode;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public int getPostcode() {
return postcode;
}
public void setPostcode(int postcode) {
this.postcode = postcode;
}
public School(String schoolName, int postcode) {
this.schoolName = schoolName;
this.postcode = postcode;
}
}
我们可以看到student1对象经过clone后所有属性的值都是相等的,引用类型的指向地址同样相同。然后我们又尝试了把原对象中的多个值进行修改,发现clone的对象只有部分值保持原样,而还有一部分值竟然跟着一起改动了!
这是正常的,因为浅clone只会clone引用类型的指向地址,而不是重新构造出一个新的对象并让clone出来的对象指向新的对象,所以才会出现引用对象会跟着一起变化而基本类型却保持住了原值。
值得一提的是,上文所说的浅拷贝只会克隆基本数据属性,而不会克隆引用其他对象的属性,但String对象又不属于基本属性,这又是为什么呢?
这是因为String对象是不可修改的对象,每次修改其实都是新建一个新的对象,而不是在原有的对象上修改,所以当修改String属性时其实是新开辟一个空间存储String对象,并把引用指向该内存,而克隆出来的对象的String属性还是指向原有的内存地址,所以String对象在浅克隆中也表现得与基本属性一样。
深克隆
如果要实现深克隆,我们需要改写clone方法。首先我们需要让类中的其他引用对象也实现Cloneable方法,同时生成自己的clone()方法。然后改写主类中的clone方法:
@Override
protected Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone(); //调用父类的clone方法
student.setStudentSchool(student.getStudentSchool().clone());
return student;
}
但是很可惜,这种方法效率十分低下,并且像是List,数组之类的数据结构并没有实现Cloneable接口,只能手动的构建新List/Set/Map/数组,非常麻烦。
其实还是有更简单的方式实现深克隆的:利用序列化和反序列化让所有的对象都在堆里的新空间内构建。我还是给大家展示一下完整代码,以便复制在本地尝试:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;
public class CloneTest {
public static void main(String[] args)
throws CloneNotSupportedException, IOException, ClassNotFoundException {
Student student1 = new Student("小明", 18, new School("一号小学", 10000));
Student student2 = student1.deepClone();
System.out.println(student2.getName());
System.out.println(student2.getName() == student1.getName()); // false
System.out.println(Objects.equals(student1.getName(), student2.getName())); // true
System.out.println(student1.getAge() == student2.getAge()); // true
System.out.println(student1.getStudentSchool() == student2.getStudentSchool()); // false
System.out.println(Objects.equals(student1.getStudentSchool().getSchoolName(),
student2.getStudentSchool().getSchoolName())); // true
}
}
class Student implements Cloneable, Serializable {
private static final long serialVersionUID = -3500638381984271754L;
private String name;
private int age;
private School studentSchool;
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 School getStudentSchool() {
return studentSchool;
}
public void setStudentSchool(School studentSchool) {
this.studentSchool = studentSchool;
}
public Student(String name, int age, School studentSchool) {
this.name = name;
this.age = age;
this.studentSchool = studentSchool;
}
@Override
protected Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone(); //调用父类的clone方法
student.setStudentSchool(student.getStudentSchool().clone());
return student;
}
public Student deepClone() throws IOException, ClassNotFoundException {
//将对象写到流里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Student) ois.readObject();
}
}
class School implements Cloneable, Serializable {
private static final long serialVersionUID = -685293860357270907L;
private String schoolName;
private int postcode;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public int getPostcode() {
return postcode;
}
public void setPostcode(int postcode) {
this.postcode = postcode;
}
public School(String schoolName, int postcode) {
this.schoolName = schoolName;
this.postcode = postcode;
}
@Override
protected School clone() throws CloneNotSupportedException {
return (School) super.clone(); //调用父类的clone方法
}
}
需要注意的有几点:
- 我们使用深克隆的类因使用了序列化与反序列化,所以这个类及其属性所使用到的类都必须实现Serializable接口。
- 注意那些无法被序列化的属性。
- 注意有些属性不应该被深克隆,例如Thread对象和Socket对象。