Java基础之浅拷贝与深拷贝

时间:2021-07-23 19:49:42

含义

  • 浅拷贝:进对对象本身(包括对象中的基本变量)进行拷贝,而不拷贝对象包含的引用指向的对象。
  • 深拷贝:不仅对对象本身,而且还对对象所包含的引用指向的对象进行拷贝。
  • 深拷贝可以看做是对浅拷贝的递归。
  • 举例来说:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

克隆方法clone()

protected native Object clone() throws CloneNotSupportedException;

  • 它是一个protected修饰的native方法,因此它的实现是取决于本地代码。native方法的效率一般来说都是远高于java中的非native方法。
  • Object中的clone方法是protected的,所以要使用clone就必须继承Object类(默认)。并且为了可以使其它类调用该方法,覆写克隆方法时必须将其作用域设置为public。
  • 克隆方法返回的是一个Object对象,所以必须要经过强制类型转换。

通常克隆对象都是通过调用super.clone()方法来获取克隆对象的,所以任何克隆的过程最终都将到达java.lang.Object 的clone()方法。但是在覆写clone()方法时,这个类需要继承Clonable接口,这个接口中没有定义方法,只做为一种标识存在。如果 clone 类没有实现 Cloneable 接口,并调用了 Object 的 clone() 方法(也就是调用了 super.Clone() 方法),那么Object 的 clone() 方法就会抛出 CloneNotSupportedException 异常。

通过从源码的注释中可以看出,克隆的一些特性:

  • x.clone() != x 必须为真,也就是对于基础类型来说,其克隆后在堆中有两个独立且内容相同的内存区域。而对于引用类型来说,其引用也不相同。也就是说克隆对象和原始对象在java 堆(heap)中是两个独立的对象。
  • x.clone().getClass() == x.getClass() 他们所属的类是同一个。
  • x.clone().equals(x) 所比较的对象内容相同。

从上述的第二和第三点可以看出,克隆完全是拷贝一个独立的副本到内存中。但是由于克隆方法可以覆写,所以并不能保证克隆出来的对象能够达到(2)和(3)要求的标准,所以他们不是克隆方法所必须要求的。

浅拷贝

只对对象本身进行拷贝,而不对对象包含的引用进行拷贝。

  • 例子如下:
package com.xqq.浅拷贝;
public class Professor implements Cloneable {
private int age;
private String name;
public Professor(int age, String name) {
this.age = age;
this.name = 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 Object clone() {
Professor professor = null;
try {
professor = (Professor) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return professor;
}
@Override
public String toString() {
return "Professor name:" + name + " age:" + age;
}
}

package com.xqq.浅拷贝;
public class Student implements Cloneable {
private String name;
private int age;
private Professor professor;// 浅拷贝完之后,所指对象仍为同一个
public Student(String name, int age, Professor professor) {
this.name = name;
this.age = age;
this.professor = professor;
}
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 Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
@Override
protected Object clone() {
Student student = null;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
@Override
public String toString() {
return "Student name: " + name + " age:" + age + " professor: "
+ professor;
}
}

package com.xqq.浅拷贝;
public class TestShallowCopy {
public static void main(String[] args) {
String name = "Tom";
int age = 20;

Professor professor = new Professor(30, "Json");
Student student1 = new Student(name, age, professor);

Student student2 = (Student)student1.clone();

System.out.println("1" + student1);
System.out.println("Copy" + student2);

System.out.println("修改了学生1的教授名-------");
professor.setName("Change");

System.out.println(student1);
System.out.println(student2);
}
}
运行结果:
1Student name: Tom age:20 professor: Professor name:Json age:30
CopyStudent name: Tom age:20 professor: Professor name:Json age:30
修改了学生1的教授名-------
Student name: Tom age:20 professor: Professor name:Change age:30
Student name: Tom age:20 professor: Professor name:Change age:30
  • shallow1和shallow的内存模型

Java基础之浅拷贝与深拷贝

可以看出,基本类型可以使用浅克隆,而对于引用类型,由于引用的是内容相同,所以改变shallow1实例对象中的属性就会影响shallow2对象中的属性。

深拷贝

深拷贝与浅拷贝的主要区别在于对复合类型数据的复制,若对象中的某个字段为复合类型数据,在拷贝时,需要对该字段也进行一份拷贝,深拷贝相当于对浅拷贝进行递归。

  • 例子如下:
package com.xqq.深拷贝;

public class Professor implements Cloneable {

private int age;
private String name;
public Professor(int age, String name) {
this.age = age;
this.name = 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 Object clone(){
Professor professor = null;
try {
professor = (Professor)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return professor;
}
@Override
public String toString() {
return "Professor name:" + name + " age:" + age;
}
}

package com.xqq.深拷贝;

public class Student implements Cloneable {

private String name;
private int age;
private Professor professor;// 浅拷贝完之后,所指对象仍为同一个

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

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 Professor getProfessor() {
return professor;
}

public void setProfessor(Professor professor) {
this.professor = professor;
}

@Override
protected Object clone() {
Student student = null;
try {
student = (Student) super.clone();// 对自身对象克隆

student.setProfessor((Professor)professor.clone());//还要对该对象内部的引用变量进行克隆
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}

@Override
public String toString() {
return "Student name: " + name + " age:" + age + " professor: "
+ professor;
}
}
package com.xqq.深拷贝;
public class TestDeepCopy {
public static void main(String[] args) {
String name = "Tom";
int age = 20;

Professor professor = new Professor(30, "Json");
Student student1 = new Student(name, age, professor);

Student student2 = (Student)student1.clone();

System.out.println("1" + student1);
System.out.println("Copy" + student2);

System.out.println("修改了学生1的教授名-------");
professor.setName("Change");

System.out.println(student1);
System.out.println(student2);
}
}

运行结果:
1Student name: Tom age:20 professor: Professor name:Json age:30
CopyStudent name: Tom age:20 professor: Professor name:Json age:30
修改了学生1的教授名-------
Student name: Tom age:20 professor: Professor name:Change age:30
Student name: Tom age:20 professor: Professor name:Json age:30
  • copy1与copy2的内存模型

Java基础之浅拷贝与深拷贝

通过反序列化进行深拷贝

  • 例子如下:
package com.xqq.深拷贝之串行化;
import java.io.Serializable;
public class Professor implements Serializable {
private static final long serialVersionUID = -5625634096029788266L;
private int age;
private String name;
public Professor(int age, String name) {
this.age = age;
this.name = 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;
}
@Override
public String toString() {
return "Professor name:" + name + " age:" + age;
}
}
package com.xqq.深拷贝之串行化;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -6910385562039120926L;
private String name;
private int age;
private Professor professor;// 浅拷贝完之后,所指对象仍为同一个
public Student(String name, int age, Professor professor) {
this.name = name;
this.age = age;
this.professor = professor;
}
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 Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
public Object deepClone() throws IOException, ClassNotFoundException {
//创建输出流
ByteArrayOutputStream out = new ByteArrayOutputStream();
//输出流对象
ObjectOutputStream oos = new ObjectOutputStream(out);
//将对象写进输出流
oos.writeObject(this);

//创建输入流
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
//从输入流读出读出对象
Student student = (Student) ois.readObject();
return student;
}
@Override
public String toString() {
return "Student name: " + name + " age:" + age + " professor: "
+ professor;
}
}
package com.xqq.深拷贝之串行化;
import java.io.IOException;
public class TestShallowCopy {
public static void main(String[] args) throws ClassNotFoundException, IOException {
String name = "Tom";
int age = 20;

Professor professor = new Professor(30, "Json");
Student student1 = new Student(name, age, professor);

Student student2 = (Student)student1.deepClone();

System.out.println("1" + student1);
System.out.println("Copy" + student2);

System.out.println("修改了学生1的教授名-------");
professor.setName("Change");

System.out.println(student1);
System.out.println(student2);
}
}
运行结果:
1Student name: Tom age:20 professor: Professor name:Json age:30
CopyStudent name: Tom age:20 professor: Professor name:Json age:30
修改了学生1的教授名-------
Student name: Tom age:20 professor: Professor name:Change age:30
Student name: Tom age:20 professor: Professor name:Json age:30

但是串行化却很耗时,在一些框架中,它们往往将对象进行串行化后进行传递,耗时较多。

总结

  • 克隆方法用于创建对象的拷贝,为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException.。
  • 在克隆java对象的时候不会调用构造器
  • java提供一种叫浅拷贝(shallow copy)的默认方式实现clone,创建好对象的副本后然后通过赋值拷贝内容,意味着如果你的类包含引用类型,那么原始对象和克隆都将指向相同的引用内容,这是很危险的,因为发生在可变的字段上任何改变将反应到他们所引用的共同内容上。为了避免这种情况,需要对引用的内容进行深度克隆。
  • 按照约定,实例的克隆应该通过调用super.clone()获取,这样有助克隆对象的不变性。如:clone!=original和clone.getClass()==original.getClass(),尽管这些不是必须的。

本文参考:http://www.cnblogs.com/shuaiwhu/archive/2010/12/14/2065088.html