1. 程式人生 > 其它 >java中的深拷貝與淺拷貝

java中的深拷貝與淺拷貝

Java中深拷貝與淺拷貝

在談論深拷貝、淺拷貝之前,首先要理解什麼是值型別?什麼是引用型別?這對於理解深拷貝、淺拷貝很關鍵。

Java的世界,我們要習慣用引用去操作物件。在Java中,像陣列、類Class、列舉EnumInteger包裝類等等,就是典型的引用型別,所以操作時一般來說採用的也是引用傳遞的方式;

但是Java中基礎資料型別,如int這些基本型別,操作時一般採取的則是值傳遞的方式,所以有時候也稱它為值型別。


為了方便案例的演示,首先準備兩個類,一個是教師類,一個是學生類,這裡以一個老師只教一個學生為例,教師類中包含自己所教的學生。

  • 教師類,Teacher
public class Teacher {
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年齡
	 */
	private int age;
	/**
	 * 學生
	 */
	private Student stu;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	......
}
  • 學生類,Student
public class Student {
	/**
	 * 學號
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	public long getStuNo() {
		return stuNo;
	}

	public void setStuNo(long stuNo) {
		this.stuNo = stuNo;
	}
    ......
}

此時,這兩個類的關係如下:

淺拷貝

淺拷貝,它的特性體現在這個“淺”字上面。

  • 淺拷貝程式碼實現:
public class Teacher implements Cloneable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年齡
	 */
	private int age;
	/**
	 * 學生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		try {
			return (Teacher) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
   	......
}
  • 主程式
public class ShallowMain {
	public static void main(String[] args) {
		Student student = new Student(2016021006, "李四");
		Teacher teacher = new Teacher("魯班", 24);
		teacher.setStu(student);

		// 克隆教師物件
		Teacher teacher1 = teacher.clone();

		System.out.println(teacher == teacher1);
		// 原物件
		System.out.println(teacher);
		// 克隆物件
		System.out.println(teacher1);

		System.out.println("-------------------修改克隆物件的name以及年齡---------------------");
		// 修改克隆物件的name以及年齡
		teacher1.setName("魯班大師");
		teacher1.setAge(34);

		// 原物件
		System.out.println("原物件:" + teacher);
		// 克隆物件
		System.out.println("克隆物件:" + teacher1);

		System.out.println("-------------------修改原物件的學生屬性---------------------");
		// 修改原物件的學生屬性
		student.setStuName("張三");
		student.setStuNo(666666);

		// 原物件
		System.out.println("原物件:" + teacher);
		// 克隆物件
		System.out.println("克隆物件:" + teacher1);
	}
}

打印出的結果:


根據這裡執行的結果得出結論:

  • teacher == teacher1打印出的結果是false,則說明是建立了一個新的物件

  • 修改克隆出的物件的name和age屬性,發現並不會影響原物件的屬性值

  • 修改克隆出的物件的學生屬性,導致原物件的學生屬性也被修改了

淺拷貝中 值型別的欄位會複製一份,而引用型別的欄位拷貝的僅僅是引用地址,而該引用地址指向的實際物件空間其實只有一份。


(1)引出的問題,String型別是屬於引用型別,為什麼它也和基本資料型別一樣是複製值?後面再研究這個問題

深拷貝

深拷貝相對於淺拷貝,在複製基本資料型別時,也會將引用型別所指向的記憶體拷貝一份。如下圖。

  • 深拷貝程式碼實現:

深拷貝需要本例中的Student也實現Cloneable介面,這樣Student也可以拷貝。

  • Student類
public class Student implements Cloneable{
	/**
	 * 學號
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	@Override
	public Student clone() {
		try {
			return (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
	
	......
}
  • Teacher類
public class Teacher implements Cloneable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年齡
	 */
	private int age;
	/**
	 * 學生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		try {
            //先克隆Teacher物件
			Teacher teacher = (Teacher) super.clone();
            //克隆Student物件,復值給克隆出的teacher物件中的stu屬性
			teacher.stu = stu.clone();
			return teacher;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
	
	......
}
  • 主程式類與淺拷貝主程式程式碼一致

  • 執行結果

根據執行結果得出:克隆出的Teacher物件已經是相互獨立的互不干擾。


深拷貝的另一種實現方式:反序列化實現物件深拷貝

  • Teacher類
public class Teacher implements Serializable{
	/**
	 * 姓名
	 */
	private String name;
	/**
	 * 年齡
	 */
	private int age;
	/**
	 * 學生
	 */
	private Student stu;

	@Override
	public Teacher clone() {
		/*try {
			Teacher teacher = (Teacher) super.clone();
			teacher.stu = stu.clone();
			return teacher;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}*/

		try {
			//將當前物件序列化到位元組流
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
			objectOutputStream.writeObject(this);
			//反序列化建立物件
			ObjectInputStream objectInputStream = new ObjectInputStream(
					new ByteArrayInputStream(outputStream.toByteArray()));
			return  (Teacher) objectInputStream.readObject();
		} catch (IOException | ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}
}
  • 反序列建立物件,需要被引用的子類可以被序列化。也就是Student類需要實現Serializable介面。
public class Student implements Serializable {
	/**
	 * 學號
	 */
	private long stuNo;
	/**
	 * 姓名
	 */
	private String stuName;

	/*@Override
	public Student clone() {
		try {
			return (Student) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}*/
}
  • 主程式執行結果:

String引用型別

String常見的兩種建立方式,示例程式碼:

public class StringMain {
	public static void main(String[] args) {
		String str1 = new String("Hello");
		String str2 = "Hello";
		System.out.println(str1 == str2); //false
	}
}

str2指向的是常量池中的Hello字串

str1指向的是在堆記憶體中建立的String物件,內容是Hello字串。

在java中String通過常量賦值認為是基本資料型別,通過new關鍵字建立的物件則是引用型別。