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

        共 6106字,需瀏覽 13分鐘

         ·

        2021-03-16 21:46



        源 / 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"1865.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ì)象的具體流程,用一張圖可大致描述成如下所示:

        1. 首先,當(dāng)我們new一個(gè)對(duì)象時(shí),比如Sheep sheep = new Sheep()JVM首先就回去檢查Sheep這個(gè)符號(hào)引用所代表的類是否已經(jīng)被加載過,如果沒有就要執(zhí)行對(duì)應(yīng)類的加載過程;
        2. 聲明類型引用很簡(jiǎn)單,比如Sheep sheep = new Sheep()就會(huì)聲明一個(gè)Sheep類型的引用sheep;
        3. 第一步類加載完成以后,對(duì)象所需的內(nèi)存大小其實(shí)就已經(jīng)確定下來了,接下來JVM就會(huì)在堆上為對(duì)象分配內(nèi)存;
        4. 所謂的屬性“0”值初始化非常好理解,即為實(shí)例化對(duì)象的各個(gè)屬性賦上默認(rèn)初始化“0”值,比如int的初始化0值就是0,而一個(gè)對(duì)象的初始化0值就是null;
        5. 接下來JVM會(huì)進(jìn)行對(duì)象頭的設(shè)置,這里面就主要包括對(duì)象的運(yùn)行時(shí)數(shù)據(jù)(比如Hash碼、分代年齡、鎖狀態(tài)標(biāo)志、鎖指針、偏向線程ID、偏向時(shí)間戳等)以及類型指針(JVM通過該類型指針來確定該對(duì)象是哪個(gè)類的實(shí)例);
        6. 屬性的顯示初始化也好理解,比如定義一個(gè)類的時(shí)候,針對(duì)某個(gè)屬性字段手動(dòng)的賦值,如:private String name = "codesheep"; 就在這時(shí)候給初始化上;
        7. 最后是調(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"1865.1f );

        而且,如果我們想明確獲取類的某個(gè)構(gòu)造函數(shù),也可以在getDeclaredConstructors()函數(shù)里直接指定構(gòu)造函數(shù)傳參類型來精確控制,就像這樣:

        Constructor constructor = Sheep.class.getDeclaredConstructorString.classInteger.classFloat.class );
        Sheep sheep7 = (Sheep) constructor.newInstance( "codesheep"1865.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 avgint... 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( 224 );
        avg( 2244 );
        avg( 224456 );

        而背地里可能會(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ù)干貨與你天天見~



        瀏覽 38
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 91乱子伦国产乱子伦 | 亚洲AV无码成人片在线观看一区 | 女女被 到爽 无套 | 女人被躁到高潮嗷嗷叫动态 | 麻豆免费 成人 传媒 |