Java中對(duì)象的克隆詳解
Java對(duì)象克隆(復(fù)制)
假如想復(fù)制一個(gè)簡(jiǎn)單變量。很簡(jiǎn)單:
int apples = 5; int pears = apples;
不僅int類(lèi)型,其它七種原始數(shù)據(jù)類(lèi)型(boolean,char,byte,short,float,double.long)同樣適用于該類(lèi)情況。
但是如果你復(fù)制的是一個(gè)對(duì)象,情況就復(fù)雜了。
假設(shè)說(shuō)我是一個(gè)beginner,我會(huì)這樣寫(xiě):
class Student { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = stu1; System.out.println("學(xué)生1:" + stu1.getNumber()); System.out.println("學(xué)生2:" + stu2.getNumber()); } }
結(jié)果:
學(xué)生1:12345
學(xué)生2:12345
這里自定義學(xué)生類(lèi),該類(lèi)只有number字段。
我們新建了一個(gè)學(xué)生實(shí)例,然后將該值賦值給stu2實(shí)例。(Student stu2 = stu1;)
再看看打印結(jié)果,作為一個(gè)新手,拍了拍胸腹,對(duì)象復(fù)制不過(guò)如此,
難道真的是這樣嗎?
我們?cè)囍淖僺tu2實(shí)例的number字段,再打印結(jié)果看看:
stu2.setNumber(54321); System.out.println("學(xué)生1:" + stu1.getNumber()); // 學(xué)生1:54321 System.out.println("學(xué)生2:" + stu2.getNumber()); // 學(xué)生2:54321
為什么改變學(xué)生2的學(xué)號(hào),學(xué)生1的學(xué)號(hào)也發(fā)生變化?
原因出在(stu2 = stu1) 這一句。該語(yǔ)句是將stu1的引用賦值給stu2,
這樣,stu1和stu2指向內(nèi)存堆中同一個(gè)對(duì)象。如圖:
那么,怎樣才能達(dá)到復(fù)制一個(gè)對(duì)象呢?
是否記得萬(wàn)類(lèi)之王Object。它有11個(gè)方法,有兩個(gè)protected的方法,其中一個(gè)為clone方法。
在Java中所有的類(lèi)都是繼承自Java語(yǔ)言包中的Object類(lèi)的,查看它的源碼,發(fā)現(xiàn)里面有一個(gè)訪(fǎng)問(wèn)限定符為protected的方法clone():
/* Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. The general intent is that, for any object x, the expression: 1) x.clone() != x will be true 2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements. 3) x.clone().equals(x) will be true, this is not an absolute requirement. */ protected native Object clone() throws CloneNotSupportedException;
仔細(xì)一看,它還是一個(gè)native方法,大家都知道native方法是非Java語(yǔ)言實(shí)現(xiàn)的代碼,供Java程序調(diào)用的,因?yàn)镴ava程序運(yùn)行在JVM虛擬機(jī)上,要想訪(fǎng)問(wèn)比較底層與操作系統(tǒng)相關(guān)的就沒(méi)辦法,只能由靠近操作系統(tǒng)的語(yǔ)言來(lái)實(shí)現(xiàn)。
- 第一次聲明保證克隆對(duì)象將有單獨(dú)的內(nèi)存地址分配。
- 第二次聲明表明,原始和克隆的對(duì)象應(yīng)該具有相同的類(lèi)類(lèi)型,但它不是強(qiáng)制性的。
- 第三聲明表明,原始和克隆的對(duì)象應(yīng)該是平等的equals()方法使用,但它不是強(qiáng)制性的。
因?yàn)槊總€(gè)類(lèi)直接或間接的父類(lèi)都是Object,因此它們都含有clone()方法,但是因?yàn)樵摲椒ㄊ莗rotected,所以都不能在類(lèi)外進(jìn)行訪(fǎng)問(wèn)。
要想對(duì)一個(gè)對(duì)象進(jìn)行復(fù)制,就需對(duì)clone方法覆蓋。
為什么要克隆?
為什么需要克隆對(duì)象?直接new一個(gè)對(duì)象不行嗎?
答案是:克隆的對(duì)象可能包含一些已修改過(guò)的屬性,而new出來(lái)的對(duì)象的屬性都是初始化時(shí)的值,所以當(dāng)需要一個(gè)新的對(duì)象來(lái)保存當(dāng)前對(duì)象的“狀態(tài)”就靠clone方法。
那把這個(gè)對(duì)象的臨時(shí)屬性一個(gè)個(gè)賦值給新new的對(duì)象不也行嘛?可以是可以,但是一來(lái)麻煩不說(shuō),二來(lái),通過(guò)上面的源碼都發(fā)現(xiàn)clone是一個(gè)native方法,就是快,在底層實(shí)現(xiàn)。
提個(gè)醒,我們常見(jiàn)的Object a=new Object();Object b;b=a;這種形式的代碼復(fù)制的是引用,即對(duì)象在內(nèi)存中的地址,a和b對(duì)象仍指向同一個(gè)對(duì)象。
而通過(guò)clone方法賦值的對(duì)象跟原來(lái)的對(duì)象是同時(shí)獨(dú)立存在的。
如何實(shí)現(xiàn)克隆
介紹下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。
Java語(yǔ)言中,數(shù)據(jù)類(lèi)型分為值類(lèi)型(基本數(shù)據(jù)類(lèi)型)和引用類(lèi)型,值類(lèi)型包括int、double、byte、boolean、char等簡(jiǎn)單數(shù)據(jù)類(lèi)型,引用類(lèi)型包括類(lèi)、接口、數(shù)組等復(fù)雜類(lèi)型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類(lèi)型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。
一般步驟是(淺克隆):
1. 被復(fù)制的類(lèi)需實(shí)現(xiàn)Clonenable接口(不實(shí)現(xiàn)的話(huà)在調(diào)用clone方法會(huì)拋出CloneNotSupportedException異常), 該接口為標(biāo)記接口(不含任何方法)
2. 覆蓋clone()方法,訪(fǎng)問(wèn)修飾符設(shè)為public。方法中調(diào)用super.clone()方法得到需要的復(fù)制對(duì)象。(native為本地方法)
下面對(duì)上面那個(gè)方法進(jìn)行改造:
class Student implements Cloneable{ private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = (Student)stu1.clone(); System.out.println("學(xué)生1:" + stu1.getNumber()); // 學(xué)生1:12345 System.out.println("學(xué)生2:" + stu2.getNumber()); // 學(xué)生2:12345 stu2.setNumber(54321); System.out.println("學(xué)生1:" + stu1.getNumber()); // 學(xué)生1:12345 System.out.println("學(xué)生2:" + stu2.getNumber()); // 學(xué)生2:54321 } }
如果還不相信這兩個(gè)對(duì)象不是同一個(gè)對(duì)象,可以看看這一句:
System.out.println(stu1 == stu2); // false
上面被稱(chēng)為淺克隆。
還有一種復(fù)雜的深度復(fù)制:
我們?cè)趯W(xué)生類(lèi)里再加一個(gè)Address類(lèi)。
class Address { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
乍一看沒(méi)問(wèn)題,真的是這樣嗎?在main方法中改變addr實(shí)例的地址。
addr.setAdd("西湖區(qū)"); System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:西湖區(qū)
這就奇怪了,怎么兩個(gè)學(xué)生的地址都改變了?
原因是淺復(fù)制只復(fù)制addr變量的引用,并沒(méi)有真正的開(kāi)辟另一塊空間,將值復(fù)制后再將引用返回給新對(duì)象。
所以,為了達(dá)到真正的復(fù)制對(duì)象,而不是純粹引用復(fù)制。需要將Address類(lèi)可復(fù)制化,并且修改clone方法,完整代碼如下:
class Address implements Cloneable { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } @Override public Object clone() { Address addr = null; try{ addr = (Address)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //淺復(fù)制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度復(fù)制 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖區(qū)"); System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
結(jié)果:
學(xué)生1:123,地址:杭州市
學(xué)生2:123,地址:杭州市
學(xué)生1:123,地址:西湖區(qū)
學(xué)生2:123,地址:杭州市
到此這篇關(guān)于Java中對(duì)象的克隆詳解的文章就介紹到這了,更多相關(guān)Java對(duì)象克隆內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Online Exam在線(xiàn)考試系統(tǒng)的實(shí)現(xiàn)
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+springboot+vue+jsp+mysql+maven實(shí)現(xiàn)Online Exam在線(xiàn)考試系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11Java實(shí)現(xiàn)去掉字符串重復(fù)字母的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)去掉字符串重復(fù)字母的方法,涉及java針對(duì)字符串的遍歷、判斷、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2017-12-12Java實(shí)現(xiàn)多項(xiàng)式除法的代碼示例
今天小編就為大家分享一篇關(guān)于Java實(shí)現(xiàn)多項(xiàng)式除法的代碼示例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式
在Web應(yīng)用程序開(kāi)發(fā)中,統(tǒng)一數(shù)據(jù)返回格式對(duì)于前后端分離項(xiàng)目尤為重要,本文就來(lái)介紹一下SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07HttpClient實(shí)現(xiàn)調(diào)用外部項(xiàng)目接口工具類(lèi)的示例
下面小編就為大家?guī)?lái)一篇HttpClient實(shí)現(xiàn)調(diào)用外部項(xiàng)目接口工具類(lèi)的示例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10Java實(shí)現(xiàn)隨機(jī)驗(yàn)證碼具體代碼
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)隨機(jī)驗(yàn)證碼具體代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01