1. 克隆相关知识
1.1. 克隆的分类
克隆分为浅克隆(shallow clone)与深克隆(deep clone)两类
1.2. 浅克隆与深克隆的定义
浅克隆:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象,换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象
深克隆:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量,那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的哪些被引用的对象,换言之,深复制把要复制的对象所引用的对象都复制了一遍。
1.3. Object中的clone方法相关释义
java
的clone()
方法定义在Object类中
clone
方法将对象复制了一份并返回给调用者,一般而言clone
方法满足:
- 对任何的对象
x
都有x.clone() != x;
(即克隆对象与原对象不是同一对象) - 对任何的对象
x
,都有x.clone().getClass() == x.getClass();
(克隆对象与原对象的类型一样) - 如果对象
x
的equals()
方法定义恰当,那么x.clone().equals(x)
应该成立
1.4. java中对象的克隆具体步骤
- 为了获取对象的一份拷贝,我们可以利用
Object
类的clone
方法 - 在派生类中覆盖基类的
clone()
方法,并声明为public
[Object
类中的clone()
方法为protected
](目的是让开发者自己决定对外是隐藏还是显示该方法) - 在派生类的
clone()
方法中,调用super.clone();
- 在派生类中实现
cloneable
接口
2. 克隆的实战
被克隆的对象的类:
//要对该类所生成的对象进行克隆,那么该类要实现Cloneable接口,并且重写Object中的clone方法
public class Student implements Cloneable {
private int age;
private String name;
private Teacher teacher;
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 Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Student对象中的属性teacher的类型:
//Student类中属性teacher的类型
public class Teacher {
private int age;
private String name;
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 class CloneTest {
public static void main(String[] args) throws Exception {
//生成一个teacher对象,并进行属性填充
Teacher teacher = new Teacher();
teacher.setAge(40);
teacher.setName("Zhang");
//生成一个student对象,并进行属性填充
Student student = new Student();
student.setAge(10);
student.setName("Sai");
student.setTeacher(teacher);
//此处就是对象的克隆
Student stu2 = (Student) student.clone();
//用克隆出来的对象来改变Teacher对象的属性值
stu2.getTeacher().setAge(50);
stu2.getTeacher().setName("Long");
//打印被克隆的对象所引用的Teacher对象的属性值
System.out.println(student.getTeacher().getAge());
System.out.println(student.getTeacher().getName());
}
}
输出结果:
50
Long
通过该示例可以看出使用Object
的clone
方法来克隆对象只是浅克隆
如何修改才能使上述代码成为深克隆?
- 使
Teacher
类实现Cloneable
接口 -
Teacher
类中覆盖Object
的clone
方法如下
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
- 修改
Studnet
类中的clone
方法为如下代码
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
Teacher teacher = stu.getTeacher();
Teacher teacherClone = (Teacher) teacher.clone();
stu.setTeacher(teacherClone);
return stu;
}
这样再使用测试类进行测试的时候输出结果如下:
40
Zhang
3. 使用序列化的特性来实现深克隆
3.1. 序列化的定义
将对象写入到流里的过程叫做序列化,而把对象从流中读出来的过程则叫做反序列化,应当指出的是写在流里的是对象的一个拷贝,而原对象仍然存在于JVM
里面
在java语言里深复制一个对象常常可以先使对象实现serializable
接口,然后把对象(实际上是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象
这样做的前提是对象以及对象内部所有引用到的对象都是可序列号的,否则就需要仔细考察那些不可序列化的对象可否射程transient
,从而将之排除在复制过程之外
cloneable
与serializable
都是标示接口,里面没有提供方法
3.2. 序列化实现深克隆示例
Student类代码如下:
public class Student implements Serializable {
private int age;
private String name;
private Teacher teacher;
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 Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
//关键看此处
public Student deepCopy() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object readObject = ois.readObject();
return (Student) readObject;
}
}
Teacher类代码如下:
//Student类中属性teacher的类型
public class Teacher implements Serializable {
private int age;
private String name;
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 class CloneTest {
public static void main(String[] args) throws Exception {
//生成一个teacher对象,并进行属性填充
Teacher teacher = new Teacher();
teacher.setAge(40);
teacher.setName("Zhang");
//生成一个student对象,并进行属性填充
Student student = new Student();
student.setAge(10);
student.setName("Sai");
student.setTeacher(teacher);
//此处就是对象的深克隆
Student stu2 = student.deepCopy();
//用克隆出来的对象来改变Teacher对象的属性值
stu2.getTeacher().setAge(50);
stu2.getTeacher().setName("Long");
//打印被克隆的对象所引用的Teacher对象的属性值
System.out.println(student.getTeacher().getAge());
System.out.println(student.getTeacher().getName());
}
}
输出结果:
40
Zhang
3.3. 序列化SerialVersionUID的作用
SerialVersionUID
的作用是避开不兼容性的问题。
如果你的对象序列化后存到硬盘上面后,可是后来你却更改了类的field
(增加或减少或改名),当你反序列时,就会出现Exception
的,这样就会造成不兼容性问题。但当serialVersionUID
相同时,它就会将不一样的field
以type
的缺省值填充,这样就可以避开不兼容性的问题
网友评论