Java對象的復制三種方式
概述
在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能 會需要一個和A完全相同新對象B,並且此后對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。例如下面程序展示的情況:
結果:
為什么改變學生2的學號,學生1的學號也發生了變化呢?
原因出在(stu2 = stu1) 這一句。該語句的作用是將stu1的引用賦值給stu2,
這樣,stu1和stu2指向內存堆中同一個對象。如圖:
那么,怎么能干干凈凈清清楚楚地復制一個對象呢。在 Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求有很多途徑,
(1)將A對象的值分別通過set方法加入B對象中;
(2)通過重寫java.lang.Object類中的方法clone();
(3)通過org.apache.commons中的工具類BeanUtils和PropertyUtils進行對象復制;
(4)通過序列化實現對象的復制。
2.將A對象的值分別通過set方法加入B對象中
對屬性逐個賦值,本實例為了演示簡單就設置了一個屬性:
我們發現,屬性少對屬性逐個賦值還挺方便,但是屬性多時,就需要一直get、set了,非常麻煩。
3.重寫java.lang.Object類中的方法clone()
先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。
在Java語言中,數據類型分為值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等復雜類型。淺克隆和深克隆的主要區別在於是否支持引用類型的成員變量的復制,下面將對兩者進行詳細介紹。
3.1淺克隆
一般步驟:
1.被復制的類需要實現Clonenable接口(不實現的話在調用clone方法會拋出CloneNotSupportedException異常), 該接口為標記接口(不含任何方法)
2.覆蓋clone()方法,訪問修飾符設為public。方法中調用super.clone()方法得到需要的復制對象。(native為本地方法)
在淺克隆中,如果原型對象的成員變量是值類型,將復制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。
簡單來說,在淺克隆中,當對象被復制時只復制它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有復制。
在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆
3.2深克隆
怎么兩個學生的地址都改變了?
原因是淺復制只是復制了addr變量的引用,並沒有真正的開辟另一塊空間,將值復制后再將引用返回給新對象。
為了達到真正的復制對象,而不是純粹引用復制。我們需要將Address類可復制化,並且修改clone方法,完整代碼如下:
在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象,深克隆將原型對象的所有引用對象也復制一份給克隆對象。
簡單來說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將復制。
在Java語言中,如果需要實現深克隆,可以通過覆蓋Object類的clone()方法實現,也可以通過序列化(Serialization)等方式來實現。
(如果引用類型里面還包含很多引用類型,或者內層引用類型的類里面又包含引用類型,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現對象的深克隆。)
4.工具類BeanUtils和PropertyUtils進行對象復制
這種寫法無論多少種屬性都只需要一行代碼搞定,很方便吧!除BeanUtils外還有一個名為PropertyUtils的工具類,它也提供copyProperties()方法,作用與BeanUtils的同名方法十分相似,主要的區別在於BeanUtils提供類型轉換功能,即發現兩個JavaBean的同名屬性為不同類型時,在支持的數據類型范圍內進行轉換,而PropertyUtils不支持這個功能,但是速度會更快一些。在實際開發中,BeanUtils使用更普遍一點,犯錯的風險更低一點。
5.通過序列化實現對象的復制
序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以復制對象本身,而且可以復制其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流里將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。
調用ByteArrayOutputStream或ByteArrayInputStream對象的close方法沒有任何意義。這兩個基於內存的流只要垃圾收集器清理對象就能夠釋放資源,這一點不同於對外部資源(如文件流)的釋放。
修改克隆的Person對象person1關聯的汽車對象的品牌屬性,原來的Person對象person關聯的汽車不會受到任何影響,因為在克隆Person對象時其關聯的汽車對象也被克隆了。
基於序列化和反序列化實現的克隆不僅僅時深度克隆,更重要的是通過范型限定,可以檢查出要克隆的對象是否支持序列化,這項檢查是編譯期完成的,不是在運行時拋出異常,這種方案明顯優於使用Object類的clone方法克隆對象。