Java中對象的克隆詳解
Java對象克隆(復制)
假如想復制一個簡單變量。很簡單:
int apples = 5; int pears = apples;
不僅int類型,其它七種原始數(shù)據(jù)類型(boolean,char,byte,short,float,double.long)同樣適用于該類情況。
但是如果你復制的是一個對象,情況就復雜了。
假設說我是一個beginner,我會這樣寫:
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("學生1:" + stu1.getNumber()); System.out.println("學生2:" + stu2.getNumber()); } }
結(jié)果:
學生1:12345
學生2:12345
這里自定義學生類,該類只有number字段。
我們新建了一個學生實例,然后將該值賦值給stu2實例。(Student stu2 = stu1;)
再看看打印結(jié)果,作為一個新手,拍了拍胸腹,對象復制不過如此,
難道真的是這樣嗎?
我們試著改變stu2實例的number字段,再打印結(jié)果看看:
stu2.setNumber(54321); System.out.println("學生1:" + stu1.getNumber()); // 學生1:54321 System.out.println("學生2:" + stu2.getNumber()); // 學生2:54321
為什么改變學生2的學號,學生1的學號也發(fā)生變化?
原因出在(stu2 = stu1) 這一句。該語句是將stu1的引用賦值給stu2,
這樣,stu1和stu2指向內(nèi)存堆中同一個對象。如圖:
那么,怎樣才能達到復制一個對象呢?
是否記得萬類之王Object。它有11個方法,有兩個protected的方法,其中一個為clone方法。
在Java中所有的類都是繼承自Java語言包中的Object類的,查看它的源碼,發(fā)現(xià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;
仔細一看,它還是一個native方法,大家都知道native方法是非Java語言實現(xiàn)的代碼,供Java程序調(diào)用的,因為Java程序運行在JVM虛擬機上,要想訪問比較底層與操作系統(tǒng)相關的就沒辦法,只能由靠近操作系統(tǒng)的語言來實現(xiàn)。
- 第一次聲明保證克隆對象將有單獨的內(nèi)存地址分配。
- 第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。
- 第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。
因為每個類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因為該方法是protected,所以都不能在類外進行訪問。
要想對一個對象進行復制,就需對clone方法覆蓋。
為什么要克???
為什么需要克隆對象?直接new一個對象不行嗎?
答案是:克隆的對象可能包含一些已修改過的屬性,而new出來的對象的屬性都是初始化時的值,所以當需要一個新的對象來保存當前對象的“狀態(tài)”就靠clone方法。
那把這個對象的臨時屬性一個個賦值給新new的對象不也行嘛?可以是可以,但是一來麻煩不說,二來,通過上面的源碼都發(fā)現(xiàn)clone是一個native方法,就是快,在底層實現(xiàn)。
提個醒,我們常見的Object a=new Object();Object b;b=a;這種形式的代碼復制的是引用,即對象在內(nèi)存中的地址,a和b對象仍指向同一個對象。
而通過clone方法賦值的對象跟原來的對象是同時獨立存在的。
如何實現(xiàn)克隆
介紹下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。
Java語言中,數(shù)據(jù)類型分為值類型(基本數(shù)據(jù)類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數(shù)據(jù)類型,引用類型包括類、接口、數(shù)組等復雜類型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類型的成員變量的復制,下面將對兩者進行詳細介紹。
一般步驟是(淺克?。?/p>
1. 被復制的類需實現(xiàn)Clonenable接口(不實現(xiàn)的話在調(diào)用clone方法會拋出CloneNotSupportedException異常), 該接口為標記接口(不含任何方法)
2. 覆蓋clone()方法,訪問修飾符設為public。方法中調(diào)用super.clone()方法得到需要的復制對象。(native為本地方法)
下面對上面那個方法進行改造:
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("學生1:" + stu1.getNumber()); // 學生1:12345 System.out.println("學生2:" + stu2.getNumber()); // 學生2:12345 stu2.setNumber(54321); System.out.println("學生1:" + stu1.getNumber()); // 學生1:12345 System.out.println("學生2:" + stu2.getNumber()); // 學生2:54321 } }
如果還不相信這兩個對象不是同一個對象,可以看看這一句:
System.out.println(stu1 == stu2); // false
上面被稱為淺克隆。
還有一種復雜的深度復制:
我們在學生類里再加一個Address類。
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("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
結(jié)果:
學生1:123,地址:杭州市
學生2:123,地址:杭州市
乍一看沒問題,真的是這樣嗎?在main方法中改變addr實例的地址。
addr.setAdd("西湖區(qū)"); System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
結(jié)果:
學生1:123,地址:杭州市
學生2:123,地址:杭州市
學生1:123,地址:西湖區(qū)
學生2:123,地址:西湖區(qū)
這就奇怪了,怎么兩個學生的地址都改變了?
原因是淺復制只復制addr變量的引用,并沒有真正的開辟另一塊空間,將值復制后再將引用返回給新對象。
所以,為了達到真正的復制對象,而不是純粹引用復制。需要將Address類可復制化,并且修改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(); //淺復制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度復制 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("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖區(qū)"); System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
結(jié)果:
學生1:123,地址:杭州市
學生2:123,地址:杭州市
學生1:123,地址:西湖區(qū)
學生2:123,地址:杭州市
到此這篇關于Java中對象的克隆詳解的文章就介紹到這了,更多相關Java對象克隆內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java Online Exam在線考試系統(tǒng)的實現(xiàn)
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+springboot+vue+jsp+mysql+maven實現(xiàn)Online Exam在線考試系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式
在Web應用程序開發(fā)中,統(tǒng)一數(shù)據(jù)返回格式對于前后端分離項目尤為重要,本文就來介紹一下SpringBoot統(tǒng)一數(shù)據(jù)返回的幾種方式,具有一定的參考價值,感興趣的可以了解一下2024-07-07HttpClient實現(xiàn)調(diào)用外部項目接口工具類的示例
下面小編就為大家?guī)硪黄狧ttpClient實現(xiàn)調(diào)用外部項目接口工具類的示例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10