JAVA 對象創建與對象克隆
一、對象的4種創建方式
- new 創建
- 反射
- 克隆
- 反序列化
二、通過new創建對象
一般情況下,對象通過new 關鍵字創建,首先會在堆上給對象分配空間,然後執行構造函數進行一系列的初始化,在分配的內存空間上為一眾屬性賦值;完成初始化後再將堆區對象的引用傳遞給棧區,最終參與程序的運行。
三、反射
調用Java.lang.Class
或者java.lang.reflect.Constructor
類的newInstance()
實例方法。
四、克隆對象
它分為深拷貝和淺拷貝,通過調用對象的 clone
方法,進行對象的克隆(拷貝)
我們可以看到 clone 方法的定義:
- 首先它是一個
native
方法 - 它被
protected
修飾,那麼,它的訪問權限就隻有它的子類或者[同包:java.lang.*]; - 雖然說類都繼承自Object,當對象A引用對象B,A沒法調用:B.clone() { B -> Object.clone() } 的,因為,Object上的clone()方法被protected修飾,
- 若A一定要訪問B的clone()方法:B重寫clone()為public修飾、或者通過public方法將 clone() 訪問權限傳遞出去。
- 此外我們還需要實現:Cloneable接口,我們可以看它的定義,實際上空無一物,可以把Cloneable視為一個標記,若不實現它,當我們調用重寫的clone方法進行克隆時會拋出異常:[java.lang.CloneNotSupportedException]
- clone不會調用類的構造方法,它隻是復制堆區上的對象信息。
為瞭測試 clone 我定義瞭兩個類:
用戶信息:UserBean
package com.bokerr.canaltask.po; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class UserBean implements Cloneable, Serializable { private static final long serialVersionUID = 2022010901L; /** 基本類型、不可變類型 */ private int age; private int sex; private String name; private String home; /** 引用類型 */ private SubInfo subInfo; public UserBean(){} /*** * 重寫 clone 為 public 讓任意對象都有 clone的訪問權限 * */ @Override public UserBean clone(){ UserBean clone = null; try{ clone = (UserBean) super.clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return clone; } }
附加信息類:SubInfo
package com.bokerr.canaltask.po; import lombok.AllArgsConstructor; import lombok.Data; import java.io.Serializable; @Data @AllArgsConstructor public class SubInfo implements Cloneable, Serializable { private static final long serialVersionUID = 2022010902L; /** * SubInfo 類的屬性都是基本類型、不可變對象(String) * */ private String work; private Integer salary; private int idNum; public SubInfo(){} /** * 此處通過public 方法對外提供對象clone方法訪問權限 * */ public SubInfo selfClone(){ try{ return (SubInfo) clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return null; } /*@Override public SubInfo clone(){ try{ return (SubInfo) super.clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return null; }*/ }
淺拷貝
我們需要知道,單純調用一個對象的clone方法,它進行的是:"淺表復制",當對象的屬性都是基本類型或者不可變(final)類型時是沒有問題的;但是存在可變對象引用時,對它的拷貝並不是一個深層的拷貝,它隻拷貝該對象的引用,這樣就會造成原對象和克隆對象的修改,都會反映到該引用對象上。
關於淺拷貝看如下測試代碼:
package com.bokerr.canaltask.workerrun; import com.bokerr.canaltask.po.SubInfo; import com.bokerr.canaltask.po.UserBean; public class ExecuteTest { public static void main(String[] args){ UserBean userBean1 = new UserBean(); userBean1.setAge(25); userBean1.setSex(1); userBean1.setName("bokerr"); userBean1.setHome("貴州銅仁"); SubInfo subInfo1 = new SubInfo(); subInfo1.setIdNum(3423); subInfo1.setSalary(Integer.valueOf(15000)); subInfo1.setWork("coder"); userBean1.setSubInfo(subInfo1); System.out.println("userBean-orign:" + userBean1); UserBean userBean2 = userBean1.clone(); userBean2.setHome("貴州貴陽"); userBean2.setAge(26); SubInfo subInfo2 = userBean2.getSubInfo(); subInfo2.setSalary(Integer.valueOf(25000)); subInfo2.setWork("manager"); subInfo2.setIdNum(100002); System.out.println("######################"); System.out.println("userBean-orign:" + userBean1); System.out.println("userBean-clone:" + userBean2); } }
UserBean
的 clone
方法定義如下,我們可以看見它隻調用瞭super.clone 而對 super.clone 的返回值沒做任何修改:
@Override public UserBean clone(){ UserBean clone = null; try{ clone = (UserBean) super.clone(); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return clone; }
輸出如下,結合測試code,我們可以發現,克隆得到的對象對 SubInfo 的修改同樣體現到瞭原對象引用的 SubInfo 對象上,因為調用 super.clone 隻是一個 "淺表復制"
userBean-orign:UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=coder, salary=15000, idNum=3423)) ###################### userBean-orign:UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=manager, salary=25000, idNum=100002)) userBean-clone:UserBean(age=26, sex=1, name=bokerr, home=貴州貴陽, subInfo=SubInfo(work=manager, salary=25000, idNum=100002))
深拷貝
深拷貝生成的對象,必須擁有完全獨立的對象內存空間,拷貝對象和原對象上的修改,都不會影響對方;
前邊提到通過super.clone
調用 Object
上的clone
方法實際上進行的隻是一個淺拷貝
為瞭實現深拷貝我們則必須修改 UserBean 的clone 方法:
/*** * 重寫 clone 為 public 讓任意對象都有 clone的訪問權限 * */ @Override public UserBean clone(){ UserBean clone = null; try{ clone = (UserBean) super.clone(); /** SubInfo.selfClone() 提供SubInfo對象clone()方法權限 */ /** 克隆可變引用對象 SubInfo,並賦值給 super.clone() 返回的:UserBean 完成深拷貝 */ SubInfo cloneSub = this.subInfo.selfClone(); clone.setSubInfo(cloneSub); } catch (CloneNotSupportedException e){ e.printStackTrace(); } return clone; }
實際上除此之外,測試代碼一成不變,然後我們來看現在的輸出,可以發現對克隆對象的引用對象:SubInfo 的修改,並未使原對象的SubInfo變化
userBean-orign:UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=coder, salary=15000, idNum=3423)) ###################### userBean-orign:UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=coder, salary=15000, idNum=3423)) userBean-clone:UserBean(age=26, sex=1, name=bokerr, home=貴州貴陽, subInfo=SubInfo(work=manager, salary=25000, idNum=100002))
此時問題來瞭:你可能會說假如我的對象進行瞭多層引用呢,且引用瞭多個對象該怎麼辦呢?那我隻能一個一個去重寫 clone 方法麼?
是的你如果使用 clone 方法可能,你確實需要那樣去處理。
假如,存在如下以對象A為根節點的引用關系:
A -> B C -> E -> F G -> G D -> H I -> J -> K
我相信處理深拷貝的人會瘋掉的。。。。
那麼有更省事的方法麼? 當然有,那就是下一節提到的反序列化。
五、反序列化
- java中的序列化是將對象轉化成一個二進制字節序列,它可以持久化到磁盤文件,也可通過網絡進行傳輸;
- 而反序列化是指將該二進制字節序列,重新還原成一個對象並加載到內存中的過程。
- 對象的反序列化過程中,沒有調用任何構造函數,整個對象都是通過將,文件流中取得的數據恢復,從而得來。
- 序列化隻保存對象的屬性狀態,而不會保存對象的方法。
- 隻有實現瞭
Serializable
接口的類才能被序列化,官方建議自定義一個SerialversionUID
,若用戶沒有自定義SerialversionUID那麼會生成默認值;序列化和反序列化就是通過對比其SerialversionUID
來進行的,一旦SerialversionUID不匹配,反序列化就無法成功 - 如果一個類的屬性包含對象引用,那麼被引用的對象也將被序列化,[被引用的對象也必須實現Serializable接口,否則會拋出異常:
java.io.NotSerializableException]
- 被
static
和transient
修飾的變量不會被序列化,可以理解為:static是類屬性存在於方法區而不在堆區;transient常用於修飾涉及安全的信息,它隻能和Serializable接口一起使用,比如:用戶密碼,不應該讓它序列化之後在網絡上傳輸。
參考代碼:
package com.bokerr.canaltask.workerrun; import com.bokerr.canaltask.po.NoCloneInfo; import com.bokerr.canaltask.po.SubInfo; import com.bokerr.canaltask.po.UserBean; import org.apache.commons.lang3.SerializationUtils; import java.util.Arrays; public class ExecuteTest { public static void main(String[] args){ UserBean userBean1 = new UserBean(); userBean1.setAge(25); userBean1.setSex(1); userBean1.setName("bokerr"); userBean1.setHome("貴州銅仁"); SubInfo subInfo1 = new SubInfo(); subInfo1.setIdNum(3423); subInfo1.setSalary(Integer.valueOf(15000)); subInfo1.setWork("coder"); userBean1.setSubInfo(subInfo1); System.out.println("序列化前" + userBean1); /** 對象序列化為二進制字節序列 */ byte[] serializeBytes = SerializationUtils.serialize(userBean1); /** 反序列化還原為Java對象 */ UserBean userBeanSer = SerializationUtils.deserialize(serializeBytes); userBeanSer.getSubInfo().setSalary(800000); userBeanSer.getSubInfo().setWork("CTO"); System.out.println("反序列化" + userBeanSer); System.out.println(userBean1 == userBeanSer); } }
輸出:
序列化前UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=coder, salary=15000, idNum=3423))
反序列化UserBean(age=25, sex=1, name=bokerr, home=貴州銅仁, subInfo=SubInfo(work=CTO, salary=800000, idNum=3423))
false
我們可以發現最終輸出瞭:subInfo.work=CTO subInfo.salary=800000
,對反序列化得到的對象引用的SubInfo對象的修改,並未影響到原對象,所以可以通過反序列化進行對象的深拷貝。
六、補充
PS*有一說一:lombok 是真的好用,雖然 set、get 方法可以自動生成,但是用 lombok後明顯代碼更簡潔瞭 ;
commons-lang3
它是apache的一個工具包,我用的序列化工具來自它。
可能有小夥伴不瞭解,我還是貼一下Maven依賴吧,雖然我知道大傢都人均大佬瞭。
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
到此這篇關於JAVA 對象創建與對象克隆的文章就介紹到這瞭,更多相關JAVA 對象的創建與克隆內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!