天天看點

序列化存取實作java對象深度克隆

我們知道,在java中,将一個非原型類型類型的對象引用,指派給另一個對象的引用之後,這兩個引用就指向了同一個對象,如:

public class DeepCloneTest {
	
	private class CloneTest {
		private Long myLong = new Long(1);
	}
	
	public static void main(String args[]) {
		new DeepCloneTest().Test();
	}
	
	public void Test() {
		CloneTest ct1 = new CloneTest();
		CloneTest ct2 = ct1;
		
		// to see if ct1 and ct2 are one same reference.
		System.out.println("ct1: " + ct1);
		System.out.println("ct2: " + ct2);
		
		// if ct1 and ct2 point to one same object, then ct1.myLong == ct2.myLong.
		System.out.println("ct1.myLong: " + ct1.myLong);
		System.out.println("ct2.myLong: " + ct2.myLong);
		
		// we change ct2's myLong
		ct2.myLong = 2L;
		
		// to see whether ct1's myLong was changed.
		System.out.println("ct1.myLong: " + ct1.myLong);
		System.out.println("ct2.myLong: " + ct2.myLong);
	}
}
           

out put:

ct1: [email protected]

ct2: [email protected]

ct1.myLong: 1

ct2.myLong: 1

ct1.myLong: 2

ct2.myLong: 2

這個很easy,估計學java的都知道(不知道的是學java的麼?)。

在記憶體中,對象的引用存放在棧中,對象的資料,存放在堆中,棧中的引用指向了堆中的對象。這裡就是兩個棧中的引用,指向了堆中的同一個對象,是以,當改變了 ct2 的 myLong,可以看到,ct1 的 myLong 值也随之改變,如果用圖來表示,就很容易了解了:

序列化存取實作java對象深度克隆

左邊的是棧區,該區中有兩個引用,值相同,它們指向了右邊堆區的同一個對象。

大多時候,我們會用 java 語言的這一特性做我們想做的事情,比如,将對象的引用作為入參傳入一個方法中,在方法中,對引用所指對象做相應修改。但有時,我們希望構造出一個和已經存在的對象具有完全相同的内容,但引用不同的對象,為此,可以這樣做:

public class DeepCloneTest{
	
	// must implements Cloneable.
	private class CloneTest implements Cloneable{
		private Object o = new Object();
		
		public CloneTest clone() {
			CloneTest ct = null;
			try {
				ct = (CloneTest)super.clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
			return ct;
		}
	}
	
	public static void main(String args[]) {
		new DeepCloneTest().Test();
	}
	
	public void Test() {
		CloneTest ct1 = new CloneTest();
		CloneTest ct2 = ct1.clone();
		
		// to see if ct1 and ct2 are one same reference.
		System.out.println("ct1: " + ct1);
		System.out.println("ct2: " + ct2);
		
		// whether ct1.o == ct2.o ? yes
		System.out.println("ct1.o " + ct1.o);
		System.out.println("ct1.o " + ct1.o);
	}
}
           

out put:

ct1: [email protected]

ct2: [email protected]

ct1.o [email protected]

ct1.o [email protected]

從輸出可以看出:ct1 和 ct2 确實是兩個不同的引用,是以我們想當然的認為,ct1.o 和 ct2.o 也是兩個不同的對象了,但從輸出可以看出并非如此!ct1.o 和 ct2.o 是同一個對象!原因在于,雖然用到了克隆,但上面隻是淺度克隆,用圖形來表示:

序列化存取實作java對象深度克隆
序列化存取實作java對象深度克隆

看到上面的 o 了麼?其實是兩個對象共享的。這就相當于,你本來有一個羊圈1,裡面有一隻羊,然後你又弄了一個羊圈2,在不将羊從羊圈1裡牽出來的情況下,将羊也圈在了羊圈2中,你以為你有兩條羊了,其實呢?大家都知道。

這就是淺度克隆的結果:如果你想讓兩個對象具有獨立的 o,就必須再對 o 做克隆操作。可能有些人認為這沒有什麼,做就做呗,但想過沒有,如果不止一個 o, 還有很多很多的類似 o 的東東,你都逐一去做克隆嗎?顯然是不太現實的。

一種解決方法是:将對象先序列化存儲到流中,然後再從留中讀出對象,這樣就可以保證讀取出來的資料和之前的對象,裡面的值完全相同,就像是一個完全的拷貝。

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 DeepCloneTest {
	
	// must implements Cloneable.
	private class CloneTest implements Serializable{


		private static final long serialVersionUID = 1L;
		private Object o = new Object();
		
		public CloneTest deepClone() {
			CloneTest ct = null;
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
	            ObjectOutputStream oos = new ObjectOutputStream(baos); 
	            oos.writeObject(this);
	            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 
	            ObjectInputStream ois= new ObjectInputStream(bais); 
	            ct = (CloneTest)ois.readObject(); 
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			return ct;
		}
	}
	
	public static void main(String args[]) {
		new DeepCloneTest().Test();
	}
	
	public void Test() {
		CloneTest ct1 = new CloneTest();
		CloneTest ct2 = ct1.deepClone();
		
		// to see if ct1 and ct2 are one same reference.
		System.out.println("ct1: " + ct1);
		System.out.println("ct2: " + ct2);
		
		// whether ct1.o == ct2.o ? no
		System.out.println("ct1.o " + ct1.o);
		System.out.println("ct1.o " + ct1.o);
	}
}
           

這個時候,記憶體中的資料就是這樣的了:

序列化存取實作java對象深度克隆

克隆任務完成。