1)深克隆与浅克隆概念
首先在此做一点声明,本文所说的克隆和有的地方所说的拷贝及复制是一个概念,我比较喜欢叫克隆,下文当中全部叫“克隆”。
⑴浅克隆
被克隆对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅克隆仅仅克隆所考虑的对象,而不克隆它所引用的对象。
⑵深克隆
被克隆对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被克隆过的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要克隆的对象所引用的对象都克隆了一遍。
如果你对这个概念不太清楚,没关系可以直接看下文的代码及图例
2)Java的clone()方法
⑴clone方法将对象克隆了一份并返回给调用者。一般而言,clone()方法满足:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
⑵Java中对象的克隆
①为了获取对象的一份克隆,我们可以利用Object类的clone()方法。
②在子类中覆盖基类的clone()方法,并声明为public。
③在子类的clone()方法中,调用super.clone()。
④在子类中实现Cloneable接口。
请看如下代码:
public class Student implements java.lang.Cloneable{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
//set 和get 方法
public void clone()throws CloneNotSupportedException{
super.clone();
}
}
public class Test{
public static void main(String[] args)throws
CloneNotsupportedException{
Student s1=new Student("zhangsan",30);
//直接用等号赋值,会发现指向的是同一个对象,所以s2 的age变了之后,s1 的
age 也发生了改变。
Student s2=s1;
s2.setAge(20);
Student s3=null;
System.out.println(s1.getAge());
//注意在这里是不能直接调用object 的clone 方法的
//s3=s1.clone();会报错,因为clone 是protected 修饰的
//在Student 类中添加了clone 方法时候可以调用了
s3=(Student)s1.clone();
//需要让Student 类实现java.lang.Cloneable 接口,要注意的是cloneable接
//口是标记接口,它只是标记该类对象可以被克隆,标记接口中没有方法。
//否则会抛出CloneNotSupportedException 异常
s3.setAge(40);
//注意观察下一句s1 的age 有没有发生变化
System.out.println(s1.getAge());
}
}
说明:继承自java.lang.Object类的clone()方法是浅克隆。以下代码可以证明。
class Teacher {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student implements Cloneable {
String name;
int age;
Teacher t;// 学生1和学生2的引用值都是一样的。
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = (Student) s1.clone();
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师name为wangwu,age为40。
}
}
那应该如何实现深层次的克隆,即修改s2的老师不会影响s1的老师?代码改进如下。
class Teacher implements Cloneable {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
class Student implements Cloneable {
String name;
int age;
Teacher t;
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
stu.t = (Teacher) t.clone();
return stu;
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = (Student) s1.clone();
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师不改变。
}
}
下面我们通过一个简单的图例来说明深克隆与浅克隆是怎么一回事
如果像上面这样来实现有没有问题,大家可以思考一下,在业界都是用对象序列化来实现深克隆,下面我们就来看一下。
3)利用对象序列化来实现深克隆
把对象写到流里的过程是序列化(Serilization)过程,Java程序员又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的反序列化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个克隆,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个克隆,Java咸菜还可以回鲜。
在Java语言里深克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个克隆)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
下面为深克隆源代码。
这样做的前提是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在克隆过程之外。上例代码改进如下。
import java.io.*;
class Teacher implements Serializable {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student implements Serializable {
String name;// 常量对象。
int age;
Teacher t;// 学生1和学生2的引用值都是一样的。
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object deepClone() throws IOException, OptionalDataException,
ClassNotFoundException {
// 将对象写到流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
// 从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = null;
try {
s2 = (Student) s1.deepClone();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师不改变。
}
}