[Java基础] 深拷贝与浅拷贝

对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部 数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。

一、引言

对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部 数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝。

二、浅拷贝

1、什么是浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

在图中,SourceObject有一个int类型的属性 "field1"和一个引用类型属性"refObj"(引用ContainedObject类型的对象)。当对SourceObject做浅拷贝时,创建了CopiedObject,它有一个包含"field1"拷贝值的属性"field2"以及仍指向refObj本身的引用。由于"field1"是基本类型,所以只是将它的值拷贝给"field2",但是由于"refObj"是一个引用类型, 所以CopiedObject指向"refObj"相同的地址。因此对SourceObject中的"refObj"所做的任何改变都会影响到CopiedObject。

2、如何实现浅拷贝

<span style="font-size:14px;">package objectCopy;

class Subject {
   private String name;
   public Subject(String s) {
      name = s;
   }
   public String getName() {
      return name;
   }
   public void setName(String s) {
      name = s;
   }
}
class Student implements Cloneable {
    // 对象引用
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) {
      name = s;
      subj = new Subject(sub);
   } 

   public Subject getSubj() {
      return subj;
   } 

   public String getName() {
      return name;
   } 

   public void setName(String s) {
      name = s;
   } 

   /**
    * 重写clone()方法
    * @return
    */
   public Object clone() {
      //浅拷贝
      try {
         // 直接调用父类的clone()方法
         return super.clone();
      } catch (CloneNotSupportedException e) {
         return null;
      }
   }
}

public class CopyTest {
	public static void main(String[] args) {
		// 原始对象
		Student stud = new Student("John", "Algebra");
		System.out.println("Original Object: " + stud.getName() + " - "
				+ stud.getSubj().getName());
		// 拷贝对象
		Student clonedStud = (Student) stud.clone();
		System.out.println("Cloned Object: " + clonedStud.getName() + " - "
				+ clonedStud.getSubj().getName());
		// 原始对象和拷贝对象是否一样:
		System.out.println("Is Original Object the same with Cloned Object: "
				+ (stud == clonedStud));
		// 原始对象和拷贝对象的name属性是否一样
		System.out
				.println("Is Original Object's field name the same with Cloned Object: "
						+ (stud.getName() == clonedStud.getName()));
		// 原始对象和拷贝对象的subj属性是否一样
		System.out
				.println("Is Original Object's field subj the same with Cloned Object: "
						+ (stud.getSubj() == clonedStud.getSubj()));
		stud.setName("Dan");
		stud.getSubj().setName("Physics");
		System.out.println("Original Object after it is updated: "
				+ stud.getName() + " - " + stud.getSubj().getName());
		System.out
				.println("Cloned Object after updating original object: "
						+ clonedStud.getName() + " - "
						+ clonedStud.getSubj().getName());
	}
}</span>

输出结果如下:

Original Object: John - Algebra

Cloned Object: John - Algebra

Is Original Object the same with Cloned Object: false

Is Original Object‘s field name the same with Cloned Object: true

Is Original Object‘s field subj the same with Cloned Object: true

Original Object after it is updated: Dan - Physics

Cloned Object after updating original object: John - Physics

在这个例子中,我让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。从输出结果中我们可以看到,对原始对象stud的"name"属性所做的改变并没有影响到拷贝对象clonedStud,但是对引用对象subj的"name"属性所做的改变影响到了拷贝对象clonedStud。

三、深拷贝

1、什么是深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

在上图中,SourceObject有一个int类型的属性 "field1"和一个引用类型属性"refObj1"(引用ContainedObject类型的对象)。当对SourceObject做深拷贝时,创建了CopiedObject,它有一个包含"field1"拷贝值的属性"field2"以及包含"refObj1"拷贝值的引用类型属性"refObj2" 。因此对SourceObject中的"refObj"所做的任何改变都不会影响到CopiedObject

2、如何实现深拷贝

<span style="font-size:14px;">package objectCopy;

class Subject {
   private String name;
   public Subject(String s) {
      name = s;
   }
   public String getName() {
      return name;
   }
   public void setName(String s) {
      name = s;
   }
}
/*class Student implements Cloneable {
    // 对象引用
   private Subject subj; 

   private String name; 

   public Student(String s, String sub) {
      name = s;
      subj = new Subject(sub);
   } 

   public Subject getSubj() {
      return subj;
   } 

   public String getName() {
      return name;
   } 

   public void setName(String s) {
      name = s;
   } 

   *//**
    * 重写clone()方法
    * @return
    *//*
   public Object clone() {
      //浅拷贝
      try {
         // 直接调用父类的clone()方法
         return super.clone();
      } catch (CloneNotSupportedException e) {
         return null;
      }
   }
}*/

class Student implements Cloneable {
	   // 对象引用
	   private Subject subj; 

	   private String name; 

	   public Student(String s, String sub) {
	      name = s;
	      subj = new Subject(sub);
	   } 

	   public Subject getSubj() {
	      return subj;
	   } 

	   public String getName() {
	      return name;
	   } 

	   public void setName(String s) {
	      name = s;
	   } 

	   /**
	    * 重写clone()方法
	    *
	    * @return
	    */
	   public Object clone() {
	      // 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
	      Student s = new Student(name, subj.getName());
	      return s;
	   }
	}

public class CopyTest {
	public static void main(String[] args) {
		// 原始对象
		Student stud = new Student("John", "Algebra");
		System.out.println("Original Object: " + stud.getName() + " - "
				+ stud.getSubj().getName());
		// 拷贝对象
		Student clonedStud = (Student) stud.clone();
		System.out.println("Cloned Object: " + clonedStud.getName() + " - "
				+ clonedStud.getSubj().getName());
		// 原始对象和拷贝对象是否一样:
		System.out.println("Is Original Object the same with Cloned Object: "
				+ (stud == clonedStud));
		// 原始对象和拷贝对象的name属性是否一样
		System.out
				.println("Is Original Object's field name the same with Cloned Object: "
						+ (stud.getName() == clonedStud.getName()));
		// 原始对象和拷贝对象的subj属性是否一样
		System.out
				.println("Is Original Object's field subj the same with Cloned Object: "
						+ (stud.getSubj() == clonedStud.getSubj()));
		stud.setName("Dan");
		stud.getSubj().setName("Physics");
		System.out.println("Original Object after it is updated: "
				+ stud.getName() + " - " + stud.getSubj().getName());
		System.out
				.println("Cloned Object after updating original object: "
						+ clonedStud.getName() + " - "
						+ clonedStud.getSubj().getName());
	}
}</span>

输出结果如下:

Original Object: John - Algebra

Cloned Object: John - Algebra

Is Original Object the same with Cloned Object: false

Is Original Object‘s field name the same with Cloned Object: true

Is Original Object‘s field subj the same with Cloned Object: false

Original Object after it is updated: Dan - Physics

Cloned Object after updating original object: John - Algebra

很容易发现clone()方法中的一点变化。因为它是深拷贝,所以你需要创建拷贝类的一个对象。因为在Student类中有对象引用,所以需要在Student类中实现Cloneable接口并且重写clone方法。

3、通过序列化实现深拷贝

也可以通过序列化来实现深拷贝。序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。

<span style="font-size:14px;">package objectCopy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class ColoredCircle implements Serializable {
	/**
	 * 生成序列化UID
	 */
	private static final long serialVersionUID = 3306829010445297199L;

	private int x;
	private int y;

	public ColoredCircle(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}

	@Override
	public String toString() {
		return "x=" + x + ", y=" + y;
	}
}

public class SerializableTest {

	public static void main(String[] args) throws IOException {
		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;

		try {
			// 创建原始的可序列化对象
			ColoredCircle c1 = new ColoredCircle(100, 100);
			System.out.println("Original = " + c1);

			ColoredCircle c2 = null;

			// 通过序列化实现深拷贝
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			// 序列化以及传递这个对象
			oos.writeObject(c1);
			oos.flush();
			ByteArrayInputStream bin = new ByteArrayInputStream(
					bos.toByteArray());
			ois = new ObjectInputStream(bin);
			// 返回新的对象
			c2 = (ColoredCircle) ois.readObject();

			// 校验内容是否相同
			System.out.println("Copied   = " + c2);
			// 改变原始对象的内容
			c1.setX(200);
			c1.setY(200);
			// 查看每一个现在的内容
			System.out.println("Original = " + c1);
			System.out.println("Copied   = " + c2);
		} catch (Exception e) {
			System.out.println("Exception in main = " + e);
		} finally {
			oos.close();
			ois.close();
		}
	}
}</span>

输出结果如下:

Original = x=100, y=100

Copied = x=100, y=100

Original = x=200, y=200

Copied = x=100, y=100

这里,你只需要做以下几件事儿:

(1)确保对象图中的所有类都是可序列化的

(2)创建输入输出流

(3)使用这个输入输出流来创建对象输入和对象输出流

(4)将你想要拷贝的对象传递给对象输出流

(5)从对象输入流中读取新的对象并且转换回你所发送的对象的类

在这个例子中,我创建了一个ColoredCircle对象c1然后将它序列化 (将它写到ByteArrayOutputStream中). 然后我反序列化这个序列化后的对象并将它保存到c2中。随后我修改了原始对象c1。然后结果如你所见,c1不同于c2,对c1所做的任何修改都不会影响c2。

注意,序列化这种方式有其自身的限制和问题:

因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。

再就是性能问题。创建一个socket, 序列化一个对象, 通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。所以在性能上会有天壤之别。如果性能对你的代码来说是至关重要的,建议不要使用这种方式。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间。

四、延迟拷贝

延迟拷贝是浅拷贝和深拷贝的一个组合,实际上很少会使用。 当最开始拷贝一个对象时,会使用速度较快的浅拷贝,还会使用一个计数器来记录有多少对象共享这个数据。当程序想要修改原始的对象时,它会决定数据是否被共享(通过检查计数器)并根据需要进行深拷贝。

延迟拷贝从外面看起来就是深拷贝,但是只要有可能它就会利用浅拷贝的速度。当原始对象中的引用不经常改变的时候可以使用延迟拷贝。由于存在计数器,效率下降很高,但只是常量级的开销。而且, 在某些情况下, 循环引用会导致一些问题。

五、如何选择

如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。我的意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。

时间: 2024-07-30 17:55:42

[Java基础] 深拷贝与浅拷贝的相关文章

浅析java的深拷贝与浅拷贝

(转自:http://www.cnblogs.com/chenssy/p/3308489.html) 首先来看看浅拷贝和深拷贝的定义: 浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝. 深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值.这个方式称为深拷贝 也就是说浅拷贝只复制一个对象,传递引用,不能复制实例.而深拷贝对对象内部的引用均复制,它是创建一个新的实例,并且复制实例. 对于

Java clone()深拷贝、浅拷贝

Cloneable接口是一个空接口,仅用于标记对象,Cloneable接口里面是没有clone()方法,的clone()方法是Object类里面的方法!默认实现是一个Native方法 protected native Object clone() throws CloneNotSupportedException; 如果对象implement Cloneable接口的话,需要覆盖clone方法(因为Object类的clone方法是protected,需要覆盖为public) public Obj

java的深拷贝和浅拷贝

import java.util.*; import java.io.*; class User implements Serializable{ public String name; public int age; public User(String str,int num){ name=str; age=num; } public String toString(){ return "Name:"+name+"\n"+"Age:"+age

Java实现深拷贝和浅拷贝

1.类实现Cloneable才可以进行对象拷贝 2.Cloneable只实现浅拷贝,需要实现深拷贝的必须要重写clone()方法 3.利用反序列化也可以实现深拷贝,但是反序列化耗时较长 n.浅拷贝是指拷贝对象时只拷贝对象本身和其基本变量及引用变量,不拷贝对象中引用指向的对象,深拷贝反之 4.可以浅拷贝的对象类 1 package deepCopy; 2 3 import java.io.Serializable; 4 5 public class UserShallowClone impleme

睡不着,复习一下C++基础中的基础(深拷贝与浅拷贝)

#include <iostream> #include <string> #include <assert.h> using namespace std; //声明字符串拷贝函数 char* mystrcpy(char* str1,const char* str2); class CPerson { char* m_pName; public: CPerson(char* pName) { cout<<"普通构造函数"<<e

Java基础提高篇

一.Java中的四舍五入 public static void test(){ System.out.println(Math.round(12.5d)); System.out.println(Math.round(-12.5d)); //使用银行家算法 BigDecimal d=new BigDecimal(100000); BigDecimal r=new BigDecimal(0.0187*3); BigDecimal i=d.multiply(r).setScale(2,Roundin

Java基础之浅拷贝与深拷贝

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

【C++基础 02】深拷贝和浅拷贝

我的主题是,每天积累一点点. =========================================== 在类定义中,如果没有提供自己的拷贝构造函数,则C++提供一个默认拷贝构造函数. C++提供的默认拷贝构造函数的工作方法是:完成一个成员一个成员的拷贝.如果成员是类对象,则条用其拷贝构造函数或者默认拷贝构造函数. 简单的自定义拷贝构造函数: class Student{ public: //拷贝构造函数 Student(Student& s) { a = s.a; } protec

Java中的深拷贝和浅拷贝 原型模式

1: Java中浅拷贝和深拷贝的定义:      浅拷贝:就是指两个对象共同拥有同一个值,一个对象改变了该值,也会影响到另一个对象.      深拷贝:就是两个对象的值相等,但是互相独立. (深拷贝才是真正的拷贝,浅拷贝只是将引用指向了同一份对象) 2:Java中几种常见的拷贝操作: (1)"="操作:也就是赋值操作: (2)拷贝构造函数:拷贝构造函数就是构造函数的参数的类型是该构造函数所在的类,即参数就是该类的一个对象. <span style="font-size: