Java中Cloneable接口
原文地址:Java中Cloneable接口作者:我有一個夢
目錄預備知識
為什麼要clone
Object的clone以及為什麼如此實現
如何clone
對clone的態度
其他的選擇
和Serializable的比較
性能
預備知識
為了理解java的clone,有必要先溫習以下的知識。
java的類型,java的類型分為兩大類,一類為primitive,如int,另一類為引用類型,如String,Object等等。
java引用類型的存儲,java的引用類型都是存儲在堆上的。
Java代碼
public class B {
int a;
Stringb;
public B(inta, String b) {
super();
this.a =a;
this.b =b;
}
}
對這樣一個引用類型的實例,我們可以推測,在堆上它的內存存儲形式(除去指向class的引用,鎖的管理等等內務事務所占內存),應該有一個int值表示a,以及一個引用,該引用指向b在堆上的存儲空間。
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/zudzjmj2ba4.gif)
為什麼要clone
恩,因為需要。廢話。
有名的GoF設計模式裏有一個模式為原型模式,用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象.
簡單的說就是clone一個對象實例。使得clone出來的copy和原有的對象一模一樣。
插一個簡單使用clone的例子,如果一個對象內部有可變對象實例的話,publicAPI不應該直接返回該對象的引用,以防調用方的code改變該對象的內部狀態。這個時候可以返回該對象的clone。
問題來了,什麼叫一模一樣。
一般來說,有
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 {
Stringname;
intage;
}
class Account implements Cloneable {
Useruser;
longbalance;
@Override
publicObject clone() throws CloneNotSupportedException {
returnsuper.clone();
}
}
//user
User user = new User();
user.name = "user";
user.age = 20;
// account.
Account account = newAccount();
account.user = user;
account.balance = 10000;
// copy.
Account copy = (Account)account.clone();
//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);
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/v2xwplr5lqb.gif)
恩,默認實現是幫了我們一些忙,但是不是全部。
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();
}
}
// user.
User user = new User();
user.name = "user";
user.age = 20;
// copy
User copy = (User)user.clone();
// 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和不可變對象類型是可以同等對待的。
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是一種浪費。Shadowcopy給了程序員更好的靈活性。
如何clone
clone三部曲。
1 聲明實現Cloneable接口。
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();
}
}
classAccount 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;
}
}
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/ez5qzlmt1yn.gif)
對clone的態度
clone嘛,我覺得是個好東西,畢竟係統默認實現已經幫我們做了很多事情了。
但是它也是有缺點的。
1 手工維護clone的調用鏈。這個問題不大,程序員有責任做好。
2 如果class的field是個final的可變類,就不行了。三部曲的第三步沒有辦法做了。
考慮一個類對clone的態度,有如下幾種。
1 公開支持:好吧,按照clone三部曲實現吧。前提是父類支持(公開或者默默)。
2默默支持:不實現Cloneable接口,但是在類裏麵有正確的protected的clone實現,這樣,該類不支持clone,但是它的子類如果想支持的話也不妨礙。
3 不支持:好吧,為了明確該目的,提供一個拋CloneNotSupportedException異常的protected的clone實現。
4 看情況支持:該類內部可以保存其他類的實例,如果其他類支持則該類支持,如果其他類不支持,該類沒有辦法,隻有不支持。
其他的選擇
可以用原型構造函數,或者靜態copy方法來手工製作一個對象的copy。
好處是即使class的field為final,也不會影響該方法的使用。不好的地方是所有的primitive賦值都得自己維護。
和Serializable的比較
使用Serializable同樣可以做到對象的clone。但是:
Cloneable本身就是為clone設計的,雖然有一些缺點,但是如果它可以clone的話無疑用它來做clone比較合適。如果不行的話用原型構造函數,或者靜態copy方法也可以。
Serializable製作clone的話,添加了太多其它的東西,增加了複雜性。
1 所有的相關的類都得支持Serializable。這個相比支持Cloneable隻會工作量更大
2Serializable添加了更多的意義,除了提供一個方法用Serializable製作Clone,該類等於也添加了其它的publicAPI,如果一個類實現了Serializable,等於它的2進製形式就已經是其API的一部分了,不便於該類以後內部的改動。
3當類用Serializable來實現clone時,用戶如果保存了一個老版本的對象2進製,該類升級,用戶用新版本的類反係列化該對象,再調用該對象用Serializable實現的clone。這裏為了一個clone的方法又引入了類版本兼容性的問題。不劃算。
性能
不可否認,JVM越來越快了。
但是係統默認的native實現還是挺快的。
clone一個有100個元素的int數組,用係統默認的clone比靜態copy方法快2倍左右。
原文地址:https://www.javaeye.com/topic/483469
可以對考的文章:https://blog.sina.com.cn/s/blog_62c1c11f0100fpht.html
https://student.csdn.net/space.php?uid=111253&do=blog&id=8905
int a;
Stringb;
public B(inta, String b) {
super();
this.a =a;
this.b =b;
}
}
對這樣一個引用類型的實例,我們可以推測,在堆上它的內存存儲形式(除去指向class的引用,鎖的管理等等內務事務所占內存),應該有一個int值表示a,以及一個引用,該引用指向b在堆上的存儲空間。
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/zudzjmj2ba4.gif)
為什麼要clone
恩,因為需要。廢話。
有名的GoF設計模式裏有一個模式為原型模式,用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象.
簡單的說就是clone一個對象實例。使得clone出來的copy和原有的對象一模一樣。
插一個簡單使用clone的例子,如果一個對象內部有可變對象實例的話,publicAPI不應該直接返回該對象的引用,以防調用方的code改變該對象的內部狀態。這個時候可以返回該對象的clone。
問題來了,什麼叫一模一樣。
一般來說,有
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的堆上內存的內容複製到這個新分配的內存空間上。
看例子。
Java代碼
Stringname;
intage;
}
class Account implements Cloneable {
Useruser;
longbalance;
@Override
publicObject clone() throws CloneNotSupportedException {
returnsuper.clone();
}
}
Java代碼
User user = new User();
user.name = "user";
user.age = 20;
// account.
Account account = newAccount();
account.user = user;
account.balance = 10000;
// copy.
Account copy = (Account)account.clone();
//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);
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/v2xwplr5lqb.gif)
恩,默認實現是幫了我們一些忙,但是不是全部。
primitive的確做到了相等且隔離。
引用類型僅僅是複製了一下引用,copy和原型引用的東西是一樣的。
這個就是所謂的淺copy了。
要實現深copy,即複製原型中對象的內存copy,而不僅僅是一個引用。隻有自己動手了。
等等,是不是所有的引用類型都需要深copy呢?
不是!
我們之所以要深copy,是因為默認的實現提供的淺copy不是隔離的,換言之,改變copy的東西,會影響到原型的內部。比如例子中,改變copy的user的name,影響了原型。
如果我們要copy的類是不可變的呢,如String,沒有方法可以改變它的內部狀態呢。
Java代碼
String name;
int age;
@Override
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
Java代碼
User user = new User();
user.name = "user";
user.age = 20;
// copy
User copy = (User)user.clone();
// 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和不可變對象類型是可以同等對待的。
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是一種浪費。Shadowcopy給了程序員更好的靈活性。
如何clone
clone三部曲。
1 聲明實現Cloneable接口。
2調用super.clone拿到一個對象,如果父類的clone實現沒有問題的話,在該對象的內存存儲中,所有父類定義的field都已經clone好了,該類中的primitive和不可變類型引用也克隆好了,可變類型引用都是淺copy。
3 把淺copy的引用指向原型對象新的克隆體。
給個例子。
Java代碼
String name;
int age;
@Override
public User clone() throws CloneNotSupportedException {
return (User) super.clone();
}
}
classAccount 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;
}
}
![[轉載]Java中Cloneable接口 [轉載]Java中Cloneable接口](https://res.3425.com.cn/aliyunqi/ez5qzlmt1yn.gif)
對clone的態度
clone嘛,我覺得是個好東西,畢竟係統默認實現已經幫我們做了很多事情了。
但是它也是有缺點的。
1 手工維護clone的調用鏈。這個問題不大,程序員有責任做好。
2 如果class的field是個final的可變類,就不行了。三部曲的第三步沒有辦法做了。
考慮一個類對clone的態度,有如下幾種。
1 公開支持:好吧,按照clone三部曲實現吧。前提是父類支持(公開或者默默)。
2默默支持:不實現Cloneable接口,但是在類裏麵有正確的protected的clone實現,這樣,該類不支持clone,但是它的子類如果想支持的話也不妨礙。
3 不支持:好吧,為了明確該目的,提供一個拋CloneNotSupportedException異常的protected的clone實現。
4 看情況支持:該類內部可以保存其他類的實例,如果其他類支持則該類支持,如果其他類不支持,該類沒有辦法,隻有不支持。
其他的選擇
可以用原型構造函數,或者靜態copy方法來手工製作一個對象的copy。
好處是即使class的field為final,也不會影響該方法的使用。不好的地方是所有的primitive賦值都得自己維護。
和Serializable的比較
使用Serializable同樣可以做到對象的clone。但是:
Cloneable本身就是為clone設計的,雖然有一些缺點,但是如果它可以clone的話無疑用它來做clone比較合適。如果不行的話用原型構造函數,或者靜態copy方法也可以。
Serializable製作clone的話,添加了太多其它的東西,增加了複雜性。
1 所有的相關的類都得支持Serializable。這個相比支持Cloneable隻會工作量更大
2Serializable添加了更多的意義,除了提供一個方法用Serializable製作Clone,該類等於也添加了其它的publicAPI,如果一個類實現了Serializable,等於它的2進製形式就已經是其API的一部分了,不便於該類以後內部的改動。
3當類用Serializable來實現clone時,用戶如果保存了一個老版本的對象2進製,該類升級,用戶用新版本的類反係列化該對象,再調用該對象用Serializable實現的clone。這裏為了一個clone的方法又引入了類版本兼容性的問題。不劃算。
性能
不可否認,JVM越來越快了。
但是係統默認的native實現還是挺快的。
clone一個有100個元素的int數組,用係統默認的clone比靜態copy方法快2倍左右。
原文地址:https://www.javaeye.com/topic/483469
可以對考的文章:https://blog.sina.com.cn/s/blog_62c1c11f0100fpht.html
https://student.csdn.net/space.php?uid=111253&do=blog&id=8905
最後更新:2017-04-03 16:48:36