对象克隆以及clone()方法实现时的深拷贝和浅拷贝

时间:2021-11-14 19:50:07

顾名思义,对象克隆,就是指对象要重新复制一份。在Java里提到clone技术,就不能不提java.lang.Cloneable接口和含有clone方法的Object类,所有具有clone()功能的类都有一个特征,那就是它直接或者间接实现了Cloneable接口。否则,如果没有实现Cloneable接口,那么在尝试调用clone()方法时,将会触发CloneNotSupportedException异常。


那么,如果一个对象需要被克隆,那么该如何实现呢?分成如下两步:

1)实现Cloneable接口

2)复写Object类中的clone()方法


一个类,如果没有实现Cloneable接口,但是你却去调用clone()方法,那么它会引发CloneNotSupportedException异常。实现了Coneable接口就说明这个类可以被克隆。如果这个类要被克隆,那实现Cloneable接口之后,就要重新复写clone()方法,当然也可以不复写这个方法。


请看下面例子:

package com.itjob.others;


class Teacher implements Cloneable{
private String name;
private int age;

public Teacher(){}
public Teacher(String name, int age) {
super();
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 Teacher clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
}


public class TestCase {


public static void main(String[] args) {
Teacher th1 = new Teacher("wangyuan",26);
try {
Teacher th2 = th1.clone();
th2.setName("liming");
th2.setAge(27);
/*
* 结果:深拷贝
* th1.name = wangyuan,th1.age = 26
* th2.name = liming,th2.age = 27
*/
System.out.println(th1 == th2); //false
System.out.println("th1.name = " + th1.getName() + "," + "th1.age = " + th1.getAge());
System.out.println("th2.name = " + th2.getName() + "," + "th2.age = " + th2.getAge());
} catch (CloneNotSupportedException e) {
System.out.println("sorry,copy operation is not allowed~");
}
}


}

对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝

从结果中可以看出,虽然th1和th2的成员变量的内容一模一样,但是th1和跟th2的堆区地址却是不同的,实现了深拷贝,深克隆。


再看:

Professor类:


对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝package com.itjob.pojo;


public class Professor implements Cloneable{
private String name;
private int age;

public Professor(){}
public Professor(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 Professor clone() throws CloneNotSupportedException {
return (Professor) super.clone();
}
}

对象克隆以及clone()方法实现时的深拷贝和浅拷贝


Student类

对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝

对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝package com.itjob.pojo;


//学生的教授是谁(包括名字和年龄).


public class Student implements Cloneable{
private String name;
private int age;
private Professor pro;

public Student(){}
public Student(String name,int age,Professor pro){
this.name = name;
this.age = age;
this.pro = pro;
}

public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void setProfessor(Professor pro){
this.pro = pro;
}
public Professor getProfessor(){
return this.pro;
}

@Override
public Student clone() throws CloneNotSupportedException {
Student s = (Student) super.clone();  //强调要拷贝的对象是Student类型的.
s.pro = pro.clone();  //强调要拷贝的对象是Professor类型的.
return s;
}
}



TestCloneOfStudent类:

对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝

对象克隆以及clone()方法实现时的深拷贝和浅拷贝对象克隆以及clone()方法实现时的深拷贝和浅拷贝package com.itjob.others;


import com.itjob.pojo.Professor;
import com.itjob.pojo.Student;


//测试深复制和浅复制


public class TestCloneOfStudent {


public static void main(String[] args) {
Professor p = new Professor("wangwu",35);
Student s1 = new Student("zhangsan",22,p);
Student s2 = null;
try {
s2 = s1.clone();
System.out.println("0--" + (s1 == s2));  //false  说明s1和s2分别指向了不同的堆区空间.
System.out.println("1--s2.name =" + s2.getName() + "," + "s2.age = " + s2.getAge());
s2.setName("lisi");
s2.setAge(23);
System.out.println("2--s1.name =" + s1.getName() + "," + "s1.age = " + s1.getAge());
System.out.println("3--s2.name =" + s2.getName() + "," + "s2.age = " + s2.getAge());
s2.getProfessor().setName("zhaoliu");
s2.getProfessor().setAge(38);
System.out.println("4--" + (s1.getProfessor() == s2.getProfessor()));  
System.out.println("5--s1.pro.name =" + s1.getProfessor().getName() + "," + "s1.pro.age = " + s1.getProfessor().getAge());
System.out.println("6--s2.pro.name =" + s2.getProfessor().getName() + "," + "s2.pro.age = " + s2.getProfessor().getAge());


/*
* 根据以上的分析,除了成员变量是基本数据类型或者是String类型的能够进行深拷贝,成员变量是一般的引用数据类型的就不能进行深拷贝,
* 只能进行浅拷贝,那么如何进行深拷贝呢?那就需要把Professor类型的对象也拷贝一份.

* 所以需要把Professor这个类实现Cloneable接口,然后复写clone方法,使Professor类能够进行复制操作;
* 做完这个操作之后,就需要在Student这个类的clone方法中写上拷贝Professor对象的操作.

* 如果在Student类中的clone()方法中没有克隆Professor对象的操作,那么上述4的结果将是true,而不是false.
* 此时就没有实现深拷贝,深克隆.

*/

} catch (CloneNotSupportedException e) {
System.out.println("sorry,the clone operation is not allowed~");
} catch (ClassCastException e) {
System.out.println("ClassCastException~");
}
}


}



浅拷贝(浅复制 或 浅克隆):如果所克隆对象所属的成员变量与原对象所属相对应的成员变量不全在同一个内存地址空间,那么这就是浅克隆。
深拷贝(深复制 或 深克隆):如果所克隆对象所属的成员变量与原对象所属相对应的成员变量都不在同一个内存地址空间,那么这就是深克隆。



利用串行化来做深复制(主要是为了避免重写比较复杂对象的深复制的clone()方法,也可以程序实现断点续传等等功能)
    把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。
    应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。
    在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
如下为深复制源代码。

?
1
2
3
4
5
6
7
8
9
10
11
public Object deepClone()
{
  //将对象写到流里
  ByteArrayOutoutStream 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());
}

这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象或属性可否设成transient,从而将之排除在复制过程之外。上例代码改进如下。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Teacher implements Serializable{
   String name;
   int age;
   public void Teacher(String name, int age){
   this .name=name;
   this .age=age;
   }
}
public class Student implements Serializable{
  String name; //常量对象
  int age;
  Teacher t; //学生1和学生2的引用值都是一样的。
  public void Student(String name, int age,Teacher t){
   this .name=name;
   this .age=age;
   this .p=p;
  }
  public Object deepClone() throws IOException,
     OptionalDataException,ClassNotFoundException{ //将对象写到流里
   ByteArrayOutoutStream 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( "tangliang" , 30 );
   Student s1= new Student( "zhangsan" , 18 ,t);
   Student s2=(Student)s1.deepClone();
   s2.t.name= "tony" ;
   s2.t.age= 40 ;
   //学生1的老师不改变
   System.out.println( "name=" +s1.t.name+ "," + "age=" +s1.t.age);
  }
}