JAVA 對(duì)象創(chuàng)建與對(duì)象克隆
一、對(duì)象的4種創(chuàng)建方式
- new 創(chuàng)建
- 反射
- 克隆
- 反序列化
二、通過new創(chuàng)建對(duì)象
一般情況下,對(duì)象通過new 關(guān)鍵字創(chuàng)建,首先會(huì)在堆上給對(duì)象分配空間,然后執(zhí)行構(gòu)造函數(shù)進(jìn)行一系列的初始化,在分配的內(nèi)存空間上為一眾屬性賦值;完成初始化后再將堆區(qū)對(duì)象的引用傳遞給棧區(qū),最終參與程序的運(yùn)行。
三、反射
調(diào)用Java.lang.Class
或者java.lang.reflect.Constructor
類的newInstance()
實(shí)例方法。
四、克隆對(duì)象
它分為深拷貝和淺拷貝,通過調(diào)用對(duì)象的 clone
方法,進(jìn)行對(duì)象的克隆(拷貝)
我們可以看到 clone 方法的定義:
- 首先它是一個(gè)
native
方法 - 它被
protected
修飾,那么,它的訪問權(quán)限就只有它的子類或者[同包:java.lang.*]; - 雖然說類都繼承自O(shè)bject,當(dāng)對(duì)象A引用對(duì)象B,A沒法調(diào)用:B.clone() { B -> Object.clone() } 的,因?yàn)?,Object上的clone()方法被protected修飾,
- 若A一定要訪問B的clone()方法:B重寫clone()為public修飾、或者通過public方法將 clone() 訪問權(quán)限傳遞出去。
- 此外我們還需要實(shí)現(xiàn):Cloneable接口,我們可以看它的定義,實(shí)際上空無一物,可以把Cloneable視為一個(gè)標(biāo)記,若不實(shí)現(xiàn)它,當(dāng)我們調(diào)用重寫的clone方法進(jìn)行克隆時(shí)會(huì)拋出異常:[java.lang.CloneNotSupportedException]
- clone不會(huì)調(diào)用類的構(gòu)造方法,它只是復(fù)制堆區(qū)上的對(duì)象信息。
為了測試 clone 我定義了兩個(gè)類:
用戶信息: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 讓任意對(duì)象都有 clone的訪問權(quán)限 ? ? ?* */ ? ? @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 類的屬性都是基本類型、不可變對(duì)象(String) ? ? ?* */ ? ? private String work; ? ? private Integer salary; ? ? private int idNum; ? ? public SubInfo(){} ? ?? ? ? /** ? ? ?* 此處通過public 方法對(duì)外提供對(duì)象clone方法訪問權(quán)限 ? ? ?* */ ? ? 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; ? ? }*/ }
淺拷貝
我們需要知道,單純調(diào)用一個(gè)對(duì)象的clone方法,它進(jìn)行的是:"淺表復(fù)制",當(dāng)對(duì)象的屬性都是基本類型或者不可變(final)類型時(shí)是沒有問題的;但是存在可變對(duì)象引用時(shí),對(duì)它的拷貝并不是一個(gè)深層的拷貝,它只拷貝該對(duì)象的引用,這樣就會(huì)造成原對(duì)象和克隆對(duì)象的修改,都會(huì)反映到該引用對(duì)象上。
關(guān)于淺拷貝看如下測試代碼:
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
方法定義如下,我們可以看見它只調(diào)用了super.clone 而對(duì) super.clone 的返回值沒做任何修改:
@Override ? ? public UserBean clone(){ ? ? ? ? UserBean clone = null; ? ? ? ? try{ ? ? ? ? ? ? clone = (UserBean) super.clone(); ? ? ? ? } catch (CloneNotSupportedException e){ ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? ? ? return clone; ? ? }
輸出如下,結(jié)合測試code,我們可以發(fā)現(xiàn),克隆得到的對(duì)象對(duì) SubInfo 的修改同樣體現(xiàn)到了原對(duì)象引用的 SubInfo 對(duì)象上,因?yàn)檎{(diào)用 super.clone 只是一個(gè) "淺表復(fù)制"
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))
深拷貝
深拷貝生成的對(duì)象,必須擁有完全獨(dú)立的對(duì)象內(nèi)存空間,拷貝對(duì)象和原對(duì)象上的修改,都不會(huì)影響對(duì)方;
前邊提到通過super.clone
調(diào)用 Object
上的clone
方法實(shí)際上進(jìn)行的只是一個(gè)淺拷貝
為了實(shí)現(xiàn)深拷貝我們則必須修改 UserBean 的clone 方法:
/*** ? ? ?* 重寫 clone 為 public 讓任意對(duì)象都有 clone的訪問權(quán)限 ? ? ?* */ ? ? @Override ? ? public UserBean clone(){ ? ? ? ? UserBean clone = null; ? ? ? ? try{ ? ? ? ? ? ? clone = (UserBean) super.clone(); ? ? ? ? ? ? /** SubInfo.selfClone() 提供SubInfo對(duì)象clone()方法權(quán)限 */ ? ? ? ? ? ? /** 克隆可變引用對(duì)象 SubInfo,并賦值給 super.clone() 返回的:UserBean 完成深拷貝 */ ? ? ? ? ? ? SubInfo cloneSub = this.subInfo.selfClone(); ? ? ? ? ? ? clone.setSubInfo(cloneSub); ? ? ? ? } catch (CloneNotSupportedException e){ ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? ? ? return clone; ? ? }
實(shí)際上除此之外,測試代碼一成不變,然后我們來看現(xiàn)在的輸出,可以發(fā)現(xiàn)對(duì)克隆對(duì)象的引用對(duì)象:SubInfo 的修改,并未使原對(duì)象的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))
此時(shí)問題來了:你可能會(huì)說假如我的對(duì)象進(jìn)行了多層引用呢,且引用了多個(gè)對(duì)象該怎么辦呢?那我只能一個(gè)一個(gè)去重寫 clone 方法么?
是的你如果使用 clone 方法可能,你確實(shí)需要那樣去處理。
假如,存在如下以對(duì)象A為根節(jié)點(diǎn)的引用關(guān)系:
A ?-> ?B ? ? ? ?C -> E -> F ? ? ? ? ? ? G -> G ? ? ? ?D -> H? ? ? ? ? ? ? I -> J -> K
我相信處理深拷貝的人會(huì)瘋掉的。。。。
那么有更省事的方法么? 當(dāng)然有,那就是下一節(jié)提到的反序列化。
五、反序列化
- java中的序列化是將對(duì)象轉(zhuǎn)化成一個(gè)二進(jìn)制字節(jié)序列,它可以持久化到磁盤文件,也可通過網(wǎng)絡(luò)進(jìn)行傳輸;
- 而反序列化是指將該二進(jìn)制字節(jié)序列,重新還原成一個(gè)對(duì)象并加載到內(nèi)存中的過程。
- 對(duì)象的反序列化過程中,沒有調(diào)用任何構(gòu)造函數(shù),整個(gè)對(duì)象都是通過將,文件流中取得的數(shù)據(jù)恢復(fù),從而得來。
- 序列化只保存對(duì)象的屬性狀態(tài),而不會(huì)保存對(duì)象的方法。
- 只有實(shí)現(xiàn)了
Serializable
接口的類才能被序列化,官方建議自定義一個(gè)SerialversionUID
,若用戶沒有自定義SerialversionUID那么會(huì)生成默認(rèn)值;序列化和反序列化就是通過對(duì)比其SerialversionUID
來進(jìn)行的,一旦SerialversionUID不匹配,反序列化就無法成功 - 如果一個(gè)類的屬性包含對(duì)象引用,那么被引用的對(duì)象也將被序列化,[被引用的對(duì)象也必須實(shí)現(xiàn)Serializable接口,否則會(huì)拋出異常:
java.io.NotSerializableException]
- 被
static
和transient
修飾的變量不會(huì)被序列化,可以理解為:static是類屬性存在于方法區(qū)而不在堆區(qū);transient常用于修飾涉及安全的信息,它只能和Serializable接口一起使用,比如:用戶密碼,不應(yīng)該讓它序列化之后在網(wǎng)絡(luò)上傳輸。
參考代碼:
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); ? ? ? ? /** 對(duì)象序列化為二進(jìn)制字節(jié)序列 */ ? ? ? ? byte[] serializeBytes = SerializationUtils.serialize(userBean1); ? ? ? ? /** 反序列化還原為Java對(duì)象 */ ? ? ? ? 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
我們可以發(fā)現(xiàn)最終輸出了:subInfo.work=CTO subInfo.salary=800000
,對(duì)反序列化得到的對(duì)象引用的SubInfo對(duì)象的修改,并未影響到原對(duì)象,所以可以通過反序列化進(jìn)行對(duì)象的深拷貝。
六、補(bǔ)充
PS*有一說一:lombok 是真的好用,雖然 set、get 方法可以自動(dòng)生成,但是用 lombok后明顯代碼更簡潔了 ;
commons-lang3
它是apache的一個(gè)工具包,我用的序列化工具來自它。
可能有小伙伴不了解,我還是貼一下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>
到此這篇關(guān)于JAVA 對(duì)象創(chuàng)建與對(duì)象克隆的文章就介紹到這了,更多相關(guān)JAVA 對(duì)象的創(chuàng)建與克隆內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javaSystem.out.println()輸出byte[]、char[]異常的問題詳析
這篇文章主要給大家介紹了關(guān)于javaSystem.out.println()輸出byte[]、char[]異常問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看啊2019-01-01Java后臺(tái)接口開發(fā)初步實(shí)戰(zhàn)教程
下面小編就為大家分享一篇 Java后臺(tái)接口開發(fā)初步實(shí)戰(zhàn)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01Spring @Lookup深入分析實(shí)現(xiàn)原理
這篇文章主要介紹了Spring @Lookup實(shí)現(xiàn)原理,我們知道在spring容器中單獨(dú)的一個(gè)抽象類是不能成為一個(gè)bean的,那么有沒有辦法呢?這個(gè)時(shí)候我們可以使用Lookup注解2023-01-01

spring-cloud-gateway動(dòng)態(tài)路由的實(shí)現(xiàn)方法

Java并發(fā) synchronized鎖住的內(nèi)容解析

ReentrantLock實(shí)現(xiàn)原理詳解

IDEA 集成 Docker 插件一鍵部署 SpringBoot 應(yīng)用