今天必須要完成一件大事!

源 / CodeSheep 文 / hansonwong99
嗯 ?
實(shí)不相瞞,在后臺(tái)時(shí)不時(shí)就會(huì)收到這樣一類私信,文字大致是這樣描述的:

看來關(guān)于「程序員找對(duì)象」這個(gè)話題,非常有必要用一篇文章來專門梳理和歸納一下了。

擇日不如撞日,今天就把這件事情給安排了吧。
可以說,方法多得很!
new一個(gè)對(duì)象
用關(guān)鍵字new進(jìn)行對(duì)象的創(chuàng)建,幾乎是寫代碼時(shí)最常用的操作之一了,比如:
Sheep sheep1 = new Sheep();
Sheep sheep2 = new Sheep( "codesheep", 18, 65.0f );
通過new的方式,我們可以調(diào)用類的無參或者有參構(gòu)造方法來實(shí)例化出一個(gè)對(duì)象。
表面上看,簡(jiǎn)簡(jiǎn)單單new一下對(duì)象就有了,但面試時(shí)如果僅僅答到這一層,大概率會(huì)撲街,因?yàn)楸冗@個(gè)更重要的是new對(duì)象時(shí)的原理和流程,因?yàn)?/span>JVM這個(gè)牽線紅娘在背后默默地幫我們做了很多工作。
說到new一個(gè)對(duì)象的具體流程,用一張圖可大致描述成如下所示:

首先,當(dāng)我們 new一個(gè)對(duì)象時(shí),比如Sheep sheep = new Sheep(),JVM首先就回去檢查Sheep這個(gè)符號(hào)引用所代表的類是否已經(jīng)被加載過,如果沒有就要執(zhí)行對(duì)應(yīng)類的加載過程;聲明類型引用很簡(jiǎn)單,比如 Sheep sheep = new Sheep()就會(huì)聲明一個(gè)Sheep類型的引用sheep;第一步類加載完成以后,對(duì)象所需的內(nèi)存大小其實(shí)就已經(jīng)確定下來了,接下來 JVM就會(huì)在堆上為對(duì)象分配內(nèi)存;所謂的屬性“ 0”值初始化非常好理解,即為實(shí)例化對(duì)象的各個(gè)屬性賦上默認(rèn)初始化“0”值,比如int的初始化0值就是0,而一個(gè)對(duì)象的初始化0值就是null;接下來JVM會(huì)進(jìn)行對(duì)象頭的設(shè)置,這里面就主要包括對(duì)象的運(yùn)行時(shí)數(shù)據(jù)(比如Hash碼、分代年齡、鎖狀態(tài)標(biāo)志、鎖指針、偏向線程ID、偏向時(shí)間戳等)以及類型指針(JVM通過該類型指針來確定該對(duì)象是哪個(gè)類的實(shí)例); 屬性的顯示初始化也好理解,比如定義一個(gè)類的時(shí)候,針對(duì)某個(gè)屬性字段手動(dòng)的賦值,如: private String name = "codesheep";就在這時(shí)候給初始化上;最后是調(diào)用類的構(gòu)造方法來進(jìn)行進(jìn)行構(gòu)造方法內(nèi)描述的初始化動(dòng)作。
應(yīng)該說,經(jīng)過了這一系列步驟,一個(gè)新的可用對(duì)象方才得以誕生。
反射出一個(gè)對(duì)象
學(xué)過Java反射機(jī)制的都知道,只要能拿到類的Class對(duì)象,就可以通過強(qiáng)大的反射機(jī)制來創(chuàng)造出實(shí)例對(duì)象了。
一般來說,拿到Class對(duì)象有三種方式:
類名.class對(duì)象名.getClass()Class.forName(全限定類名)
有了Class對(duì)象之后,接下來就可以調(diào)用其newInstance()方法來創(chuàng)建一個(gè)對(duì)象,就像這樣:
Sheep sheep3 = (Sheep) Class.forName( "cn.codesheep.article.obj.Sheep" ).newInstance();
Sheep sheep4 = Sheep.class.newInstance();
當(dāng)然,這種方式的局限性也有目共睹,因?yàn)槭褂玫氖穷惖?strong style="color: rgb(248, 57, 41);">無參構(gòu)造方法來創(chuàng)建的對(duì)象。
所以比這個(gè)更進(jìn)一步的方式是通過java.lang.relect.Constructor這個(gè)類的newInstance()方法來創(chuàng)建對(duì)象,因?yàn)樗梢悦鞔_指定某個(gè)構(gòu)造器來創(chuàng)建對(duì)象。
比如,在我們拿到了類的Class對(duì)象后,就可以通過getDeclaredConstructors()函數(shù)來獲取到類的所有構(gòu)造函數(shù)列表,這樣我們就可以調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù)來創(chuàng)建對(duì)象了,就像這樣:
Constructor<?>[] constructors = Sheep.class.getDeclaredConstructors();
Sheep sheep5 = (Sheep) constructors[0].newInstance();
Sheep sheep6 = (Sheep) constructors[1].newInstance( "codesheep", 18, 65.1f );
而且,如果我們想明確獲取類的某個(gè)構(gòu)造函數(shù),也可以在getDeclaredConstructors()函數(shù)里直接指定構(gòu)造函數(shù)傳參類型來精確控制,就像這樣:
Constructor constructor = Sheep.class.getDeclaredConstructor( String.class, Integer.class, Float.class );
Sheep sheep7 = (Sheep) constructor.newInstance( "codesheep", 18, 65.2f );
克隆出一個(gè)對(duì)象
對(duì)象克隆在我們?nèi)粘懘a的時(shí)候基本上是剛性需求,基于一個(gè)對(duì)象克隆出另一個(gè)對(duì)象,這也是寫Java代碼時(shí)十分常見的操作。
關(guān)于對(duì)象拷貝這一知識(shí)點(diǎn),之前我就寫過了,詳細(xì)梳理過一篇:《一個(gè)工作三年的同事,居然還搞不清深拷貝、淺拷貝...》,里面詳細(xì)梳理了對(duì)象賦值、拷貝、深拷貝、淺拷貝等系列知識(shí)點(diǎn),本文便不再贅述了。
反序列化出一個(gè)對(duì)象
關(guān)于對(duì)象「序列化和反序列化」這個(gè)知識(shí)點(diǎn),重要且有用,但聽很多朋友反映初學(xué)時(shí)有點(diǎn)糊。當(dāng)我們作序列化和反序列化操作時(shí),背后也會(huì)創(chuàng)建對(duì)象,關(guān)于「序列化和反序列化」這個(gè)知識(shí)點(diǎn)的詳細(xì)理解+梳理,之前我也寫過了,鏈接在此:序列化/反序列化,我忍你很久了,淦!。
Unsafe黑魔法
Unsafe類這個(gè)名字一聽就有點(diǎn)懸了,的確,我們平時(shí)的業(yè)務(wù)代碼里接觸得好像并不多。
我們都知道寫Java代碼,很少會(huì)去操作位于底層的一些資源,比如內(nèi)存等這些。而位于sun.misc.Unsafe包路徑下的Unsafe類提供了一種直接訪問系統(tǒng)資源的途徑和方法,可以進(jìn)行一些底層的操作。比如借助Unsafe我們就可以分配內(nèi)存、創(chuàng)建對(duì)象、釋放內(nèi)存、定位對(duì)象某個(gè)字段的內(nèi)存位置甚至并修改它等等。
可見這玩意誤用時(shí)的破壞力是很大的,所以一般也都是受控使用的。業(yè)務(wù)代碼里很少能看到它的身影,但是JDK內(nèi)部的一些諸如io、nio、juc等包中的代碼里還是有不少關(guān)于它的身影存在的。
Unsafe類中有一個(gè)allocateInstance()方法,通過其就可以創(chuàng)建一個(gè)對(duì)象。為此我們只需要獲取到一個(gè)Unsafe類的實(shí)例對(duì)象,我們自然就可以調(diào)用allocateInstance()來創(chuàng)建對(duì)象了。
那如何才能獲取到一個(gè)Unsafe類的實(shí)例對(duì)象呢?
大致瞅一眼Unsafe類的源碼我們就會(huì)發(fā)現(xiàn),它是一個(gè)單例類,其構(gòu)造方法是私有的,所以直接構(gòu)造是不太現(xiàn)實(shí)了:
public final class Unsafe {
private static final Unsafe theUnsafe;
// ... 省略 ...
private static native void registerNatives();
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
// ... 省略 ...
}
而且獲取單例對(duì)象的入口函數(shù)getUnsafe()上也做了特殊標(biāo)記,意思是只能從引導(dǎo)加載的類才可以調(diào)用該方法,這意味著該方法也是供JVM內(nèi)部使用的,外部代碼直接使用會(huì)報(bào)類似這樣的異常:
Exception in thread "main" java.lang.SecurityException: Unsafe
走投無路,我們只能再次重拾強(qiáng)大的反射機(jī)制來創(chuàng)建Unsafe類的實(shí)例了:
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
然后接下來我們就可以愉快地利用它來創(chuàng)建對(duì)象了:
Sheep sheep8 = (Sheep) unsafe.allocateInstance( Sheep.class );
對(duì)象的隱式創(chuàng)建場(chǎng)景
當(dāng)然除了上述這幾種顯式地對(duì)象創(chuàng)建場(chǎng)景之外,還有一些我們并沒有進(jìn)行手動(dòng)對(duì)象創(chuàng)建的隱式場(chǎng)景,舉幾個(gè)常見例子。
Class類實(shí)例隱式創(chuàng)建
我們都知道JVM虛擬機(jī)在加載一個(gè)類的時(shí)候,也都會(huì)創(chuàng)建一個(gè)類對(duì)應(yīng)的Class實(shí)例對(duì)象,很明顯這一過程是JVM偷偷地背著我們干的。
字符串隱式對(duì)象創(chuàng)建
典型的,比如定義一個(gè)String類型的字面變量時(shí),就可能會(huì)引起一個(gè)新的String對(duì)象的創(chuàng)建,就像這樣:
String name = "codesheep";
還常見的比如String的+號(hào)連接符也會(huì)隱式地導(dǎo)致新String對(duì)象的創(chuàng)建等:
String str = str1 + str2;
自動(dòng)裝箱機(jī)制
這種例子也有很多,比如在執(zhí)行類似如下代碼時(shí):
Integer codeSheepAge = 18;
其觸發(fā)的自動(dòng)裝箱機(jī)制就會(huì)導(dǎo)致一個(gè)新的包裝類型的對(duì)象在后臺(tái)被隱式地創(chuàng)建出來。
函數(shù)可變參數(shù)
比如像下面這樣,當(dāng)我們使用可變參數(shù)語法int... nums來描述一個(gè)函數(shù)的入?yún)r(shí):
public double avg( int... nums ) {
double sum = 0;
int length = nums.length;
for (int i = 0; i<length; ++i) {
sum += nums[i];
}
return sum/length;
}
從表面上看,函數(shù)的調(diào)用處可以傳入各種離散參數(shù)參與計(jì)算:
avg( 2, 2, 4 );
avg( 2, 2, 4, 4 );
avg( 2, 2, 4, 4, 5, 6 );
而背地里可能會(huì)隱式地產(chǎn)生一個(gè)對(duì)應(yīng)的數(shù)組對(duì)象進(jìn)行計(jì)算。
總而總之,很多場(chǎng)景下對(duì)象的隱式創(chuàng)建也是數(shù)見不鮮,我們最起碼要做到心中大致有數(shù)。
后 記
所以看完文章,再回到文章開頭提到的問題,你還覺得Java程序員搞對(duì)象是件難事嗎?這么多花里胡哨的對(duì)象生成法還不夠你用的么。

咳咳,玩笑歸玩笑,這其實(shí)是面試時(shí)最常問到的基礎(chǔ)問題之一。有時(shí)候面試官冷不丁問一句:“在Java里,你有哪些方式可以創(chuàng)建一個(gè)對(duì)象呢?”
所以針對(duì)該問題,這篇來好好梳理和歸納一下。
好啦,一個(gè)小小的對(duì)象創(chuàng)建就能扯出這么多的花樣,好在經(jīng)過一番梳理和總結(jié),也更便于掌握和理解了。
— 完 —
一鍵三連「分享」、「點(diǎn)贊」和「在看」
技術(shù)干貨與你天天見~
