黑馬程序員-clone方法的使用詳解

----------- android培訓java培訓、java學習型技術博客、期待與您交流! ------------

爲了理解java的clone,有必要先溫習以下的知識。

  java的類型,java的類型分爲兩大類,一類爲primitive,如int,另一類爲引用類型,如String,Object等等。

  java引用類型的存儲,java的引用類型都是存儲在堆上的

public class B {
	int a;
	String b;

	public B(int a, String b) {

		super();

		this.a = a;

		this.b = b;
	}
}


簡單的說就是clone一個對象實例。使得clone出來的copy和原有的對象一模一樣。問題來了,什麼叫一模一樣。

  一般來說,有

  x.clone() != x

  x.clone().getClass() == x.getClass()

  x.clone().equals(x)

但是這些都不是強制的。

  我們需要什麼樣的clone就搞出什麼樣的clone好了。

  一般而言,我們要的clone應該是這樣的。copy和原型的內容一樣,但是又是彼此隔離的。即在clone之後,改變其中一個不影響另外一個。

  Object的clone以及爲什麼如此實現

  Object的clone的行爲是最簡單的。以堆上的內存存儲解釋的話(不計內務內存),對一個對象a的clone就是在堆上分配一個和a在堆上所佔存儲空間一樣大的一塊地方,然後把a的堆上內存的內容複製到這個新分配的內存空間上。


 

class User {

	String name;

	int age;
}

class Account implements Cloneable {

	User user;

	long balance;

	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}


 

public static void main(String[] args) {
		// user.

		User user = new User();

		user.name = "user";

		user.age = 20;

		// account.

		Account account = new Account();

		account.user = user;

		account.balance = 10000;

		// copy.

		Account copy = null;
		try {
			copy = (Account) account.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// balance因爲是primitive,所以copy和原型是相等且獨立的。

		Assert.assertEquals(copy.balance, account.balance);

		copy.balance = 20000;

		// 改變copy不影響原型。

		Assert.assertTrue(copy.balance != account.balance);
		// user因爲是引用類型,所以copy和原型的引用是同一的。

		Assert.assertTrue(copy.user == account.user);

		copy.user.name = "newName";

		// 改變的是同一個東西。

		Assert.assertEquals("newName", account.user.name);

	}


 

恩,默認實現是幫了我們一些忙,但是不是全部。

  primitive的確做到了相等且隔離。

  引用類型僅僅是複製了一下引用,copy和原型引用的東西是一樣的。

  這個就是所謂的淺copy了。

  要實現深copy,即複製原型中對象的內存copy,而不僅僅是一個引用。只有自己動手了。

  等等,是不是所有的引用類型都需要深copy呢?

  不是!

  我們之所以要深copy,是因爲默認的實現提供的淺copy不是隔離的,換言之,改變copy的東西,會影響到原型的內部。比如例子中,改變copy的user的name,影響了原型。

  如果我們要copy的類是不可變的呢,如String,沒有方法可以改變它的內部狀態呢。

class User implements Cloneable {

	String name;

	int age;

	@Override
	public Object clone() throws CloneNotSupportedException {

		return super.clone();
	}
}


 

public static void main(String[] args) {
		// user.

		User user = new User();
		user.name = "user";
		user.age = 20;

		// copy

		User copy = null;
		try {
			copy = (User) user.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// age因爲是primitive,所以copy和原型是相等且獨立的。

		Assert.assertEquals(copy.age, user.age);

		copy.age = 30;

		// 改變copy不影響原型。

		Assert.assertTrue(copy.age != user.age);

		// name因爲是引用類型,所以copy和原型的引用是同一的。

		Assert.assertTrue(copy.name == user.name);

		// String爲不可變類。沒有辦法可以通過對copy.name的字符串的操作改變這個字符串。

		// 改變引用新的對象不會影響原型。

		copy.name = "newname";

		Assert.assertEquals("newname", copy.name);

		Assert.assertEquals("user", user.name);

	}


 

可見,在考慮clone時,primitive和不可變對象類型是可以同等對待的。

因爲對於primitive和不可變的對象(如String,Integer等)引用,它們都是在棧裏面分配存儲空間,不可變對象引用經clone後,也就在棧裏面多一個引用的拷貝,而它只會指向的是棧裏的不可修改的常量,所以它和primitive類型在clone操作後是一樣的,都會生成一個獨立的地址空間,對clone後的對象的這些屬性的修改不會影響原對象的這些屬性。

  java爲什麼如此實現clone呢?

  也許有以下考慮。

  1 效率和簡單性,簡單的copy一個對象在堆上的的內存比遍歷一個對象網然後內存深copy明顯效率高並且簡單。

  2 不給別的類強加意義。如果A實現了Cloneable,同時有一個引用指向B,如果直接複製內存進行深copy的話,意味着B在意義上也是支持Clone的,但是這個是在使用B的A中做的,B甚至都不知道。破壞了B原有的接口。

  3 有可能破壞語義。如果A實現了Cloneable,同時有一個引用指向B,該B實現爲單例模式,如果直接複製內存進行深copy的話,破壞了B的單例模式。

  4 方便且更靈活,如果A引用一個不可變對象,則內存deep copy是一種浪費。Shadow copy給了程序員更好的靈活性。

  如何clone

  clone三部曲。

  1 聲明實現Cloneable接口,這然使用如super.clone()方法時,會發生拋出cloneNotSupportedException

  2 調用super.clone拿到一個對象,如果父類的clone實現沒有問題的話,在該對象的內存存儲中,所有父類定義的field都已經clone好了,該類中的primitive和不可變類型引用也克隆好了,可變類型引用都是淺copy。

  3 把淺copy的引用指向原型對象新的克隆體。

  給個例子:

class User implements Cloneable {

	String name;

	int age;

	@Override
	public User clone() throws CloneNotSupportedException {

		return (User) super.clone();
	}
}

class Account implements Cloneable {

	User user;

	long balance;

	@Override
	public Account clone() throws CloneNotSupportedException {

		Account account = null;

		account = (Account) super.clone();

		if (user != null) {

			account.user = user.clone();
		}

		return account;
	}
}

----------- android培訓java培訓、java學習型技術博客、期待與您交流! ------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章