日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

類加載、對(duì)象實(shí)例化知識(shí)點(diǎn)一網(wǎng)打盡

 路人甲Java 2021-12-14

前言

之前說了類加載的過程,但是有的讀者表示還是有些知識(shí)點(diǎn)沒弄清楚,相關(guān)面試題也不能思考出結(jié)果,所以今天就來總結(jié)下類加載、對(duì)象實(shí)例化方面的知識(shí)點(diǎn)/面試題,幫助大家加深印象。

全是干貨,一網(wǎng)打盡類的基礎(chǔ)知識(shí)!先看看下面的問題都能回答上來嗎?

  • 描述new一個(gè)對(duì)象的過程,并結(jié)合例子說明。
  • 類初始化的觸發(fā)時(shí)機(jī)。
  • 多線程進(jìn)行類的初始化會(huì)出問題嗎?
  • 類的實(shí)例化觸發(fā)時(shí)機(jī)。
  • <clinit>()方法和<init>()方法區(qū)別。
  • 在類都沒有初始化完畢之前,能直接進(jìn)行實(shí)例化相應(yīng)的對(duì)象嗎?
  • 類的初始化過程與類的實(shí)例化過程的異同?
  • 一個(gè)實(shí)例變量在對(duì)象初始化的過程中會(huì)被賦值幾次?

描述new一個(gè)對(duì)象的過程

先上圖,再描述:

類加載鏈接

類初始化

對(duì)象實(shí)例化

Java中對(duì)象的創(chuàng)建過程包括 類初始化和類實(shí)例化兩個(gè)階段。
new就是創(chuàng)建對(duì)象的一種方式,一種時(shí)機(jī)。

當(dāng)執(zhí)行到new的字節(jié)碼指令的時(shí)候,會(huì)先判斷這個(gè)類是否已經(jīng)初始化,如果沒有初始化就要進(jìn)行類的初始化,也就是執(zhí)行類構(gòu)造器<clinit>()方法。
如果已經(jīng)初始化了,就直接進(jìn)行類對(duì)象的實(shí)例化。

  • 類的初始化,是類的生命周期中的一個(gè)階段,會(huì)為類中各個(gè)類成員賦初始值。
  • 類的實(shí)例化,是指創(chuàng)建一個(gè)類的實(shí)例的過程。

但是在類的初始化之前,JVM會(huì)保證類的裝載,鏈接(驗(yàn)證、準(zhǔn)備、解析)四個(gè)階段都已經(jīng)完成,也就是上面的第一張圖。

  • 裝載是指 Java 虛擬機(jī)查找.class文件并生成字節(jié)流,然后根據(jù)字節(jié)流創(chuàng)建java.lang.Class對(duì)象的過程。
  • 鏈接是指驗(yàn)證創(chuàng)建的類,并將其解析到JVM中使之能夠被 JVM 執(zhí)行。

那到底類加載的時(shí)機(jī)是什么時(shí)候呢?JVM 并沒有規(guī)范何時(shí)具體執(zhí)行,不同虛擬機(jī)的實(shí)現(xiàn)會(huì)有不同,常見有以下兩種情況:

  • 隱式裝載:在程序運(yùn)行過程中,當(dāng)碰到通過 new 等方式生成對(duì)象時(shí),系統(tǒng)會(huì)隱式調(diào)用 ClassLoader 去裝載對(duì)應(yīng)的 class 到內(nèi)存中;
  • 顯示裝載:在編寫源代碼時(shí),主動(dòng)調(diào)用 Class.forName() 等方法也會(huì)進(jìn)行 class 裝載操作,這種方式通常稱為顯示裝載。

所以到這里,大的流程框架就搞清楚了:

  • 當(dāng)JVM碰到new字節(jié)碼的時(shí)候,會(huì)先判斷類是否已經(jīng)初始化,如果沒有初始化(有可能類還沒有加載,如果是隱式裝載,此時(shí)應(yīng)該還沒有類加載,就會(huì)先進(jìn)行裝載、驗(yàn)證、準(zhǔn)備、解析四個(gè)階段),然后進(jìn)行類初始化。

  • 如果已經(jīng)初始化過了,就直接開始類對(duì)象的實(shí)例化工作,這時(shí)候會(huì)調(diào)用類對(duì)象的<init>方法。

結(jié)合例子說明

然后說說具體的邏輯,結(jié)合一段類代碼:

public class Run {
    public static void main(String[] args) {
        new Student();
    }
}


public class Person{
    public static int value1 = 100;
    public static final int value2 = 200;

    public int value4 = 400;

    static{
        value1 = 101;
        System.out.println("1");
    }

    {
        value1 = 102;
        System.out.println("3");
    }

    public Person(){
        value1 = 103;
        System.out.println("4");
    }
}

public class Student extends Person{
    public static int value3 = 300;

    public int value5 = 500;

    static{
        value3 = 301;
        System.out.println("2");
    }

    {
        value3 = 302;
        System.out.println("5");
    }

    public Student(){
        value3 = 303;
        System.out.println("6");
    }
}
  • 首先是類裝載,鏈接(驗(yàn)證、準(zhǔn)備、解析)。

  • 當(dāng)執(zhí)行類準(zhǔn)備過程中,會(huì)對(duì)類中的靜態(tài)變量分配內(nèi)存,并設(shè)置為初始值也就是“0值”。比如上述代碼中的value1,value3,會(huì)為他們分配內(nèi)存,并將其設(shè)置為0。但是注意,用final修飾靜態(tài)常量value2,會(huì)在這一步就設(shè)置好初始值102。

  • 初始化階段,會(huì)執(zhí)行類構(gòu)造器<clinit>方法,其主要工作就是初始化類中靜態(tài)的(變量,代碼塊)。但是在當(dāng)前類的<clinit>方法執(zhí)行之前,會(huì)保證其父類的<clinit>方法已經(jīng)執(zhí)行完畢,所以一開始會(huì)執(zhí)行最上面的父類Object的<clinit>方法,這個(gè)例子中會(huì)先初始化父類Person,再初始化子類Student。

  • 初始化中,靜態(tài)變量和靜態(tài)代碼塊順序是由語句在源文件中出現(xiàn)的順序所決定的,也就是誰寫在前面就先執(zhí)行誰。所以這里先執(zhí)行父類中的value1=100,value1 = 101,然后執(zhí)行子類中的value3 = 300,value3 = 301。

  • 接著就是創(chuàng)建對(duì)象的過程,也就是類的實(shí)例化,當(dāng)對(duì)象被類創(chuàng)建時(shí),虛擬機(jī)會(huì)分配內(nèi)存來存放對(duì)象自己的實(shí)例變量和父類繼承過來的實(shí)例變量,同時(shí)會(huì)為這些事例變量賦予默認(rèn)值(0值)。

  • 分配完內(nèi)存后,會(huì)初始化父類的普通成員變量(value4 = 400),和執(zhí)行父類的普通代碼塊(value1=102),順序由代碼順序決定。

  • 執(zhí)行父類的構(gòu)造函數(shù)(value1 = 103)

  • 父類實(shí)例化完了,就實(shí)例化子類,初始化子類的普通成員變量(value5 = 500),執(zhí)行子類的普通代碼塊(value3 = 302),順序由代碼順序決定。

  • 執(zhí)行子類的構(gòu)造函數(shù)(value3 = 303)。

所以上述例子打印的結(jié)果是:

123456

總結(jié)一下執(zhí)行流程就是:

  1. 父類靜態(tài)變量和靜態(tài)代碼塊;

  2. 子類靜態(tài)變量和靜態(tài)代碼塊;

  3. 父類普通成員變量和普通代碼塊;

  4. 父類的構(gòu)造函數(shù);

  5. 子類普通成員變量和普通代碼塊;

  6. 子類的構(gòu)造函數(shù)。

最后,大家再結(jié)合流程圖好好梳理一下:

類加載鏈接

類初始化

對(duì)象實(shí)例化

類初始化的觸發(fā)時(shí)機(jī)

在同一個(gè)類加載器下,一個(gè)類型只會(huì)被初始化一次,剛才說到new對(duì)象是類初始化的一個(gè)判斷時(shí)機(jī),其實(shí)一共有六種能夠觸發(fā)類初始化的時(shí)機(jī):

  • 虛擬機(jī)啟動(dòng)時(shí),初始化包含 main 方法的主類;

  • 遇到 new 等指令創(chuàng)建對(duì)象實(shí)例時(shí),如果目標(biāo)對(duì)象類沒有被初始化則進(jìn)行初始化操作;

  • 當(dāng)遇到訪問靜態(tài)方法或者靜態(tài)字段的指令時(shí),如果目標(biāo)對(duì)象類沒有被初始化則進(jìn)行初始化操作;

  • 子類的初始化過程如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化;

  • 使用反射 API 進(jìn)行反射調(diào)用時(shí),如果類沒有進(jìn)行過初始化則需要先觸發(fā)其初始化;

  • 第一次調(diào)用 java.lang.invoke.MethodHandle 實(shí)例時(shí),需要初始化 MethodHandle 指向方法所在的類。

多線程進(jìn)行類的初始化會(huì)出問題嗎

不會(huì),<clinit>()方法是阻塞的,在多線程環(huán)境下,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>(),其他線程都會(huì)被阻塞。

類的實(shí)例化觸發(fā)時(shí)機(jī)

  • 使用new關(guān)鍵字創(chuàng)建對(duì)象
  • 使用Class類的newInstance方法,Constructor類的newInstance方法(反射機(jī)制)
  • 使用Clone方法創(chuàng)建對(duì)象
  • 使用(反)序列化機(jī)制創(chuàng)建對(duì)象

<clinit>()方法和<init>()方法區(qū)別。

  • <clinit>()方法發(fā)生在類初始化階段,會(huì)執(zhí)行類中的靜態(tài)類變量的初始化和靜態(tài)代碼塊中的邏輯,執(zhí)行順序就是語句在源文件中出現(xiàn)的順序。

  • <init>()方法發(fā)生在類實(shí)例化階段,是默認(rèn)的構(gòu)造函數(shù),會(huì)執(zhí)行普通成員變量的初始化和普通代碼塊的邏輯,執(zhí)行順序就是語句在源文件中出現(xiàn)的順序。

在類都沒有初始化完畢之前,能直接進(jìn)行實(shí)例化相應(yīng)的對(duì)象嗎?

剛才都說了先初始化,再實(shí)例化,如果這個(gè)問題可以的話那不是打臉了嗎?

沒錯(cuò),要打臉了哈哈。

確實(shí)是先進(jìn)行類的初始化,再進(jìn)行類的實(shí)例化,但是如果我們?cè)陬惖?code>初始化階段就直接實(shí)例化對(duì)象呢?比如:

class Person {

    public static void main(String[] args) {
        staticFunction();
    }

    static Person person = new Person();

    static {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }


    Person() {
        System.out.println("3");
        System.out.println("a=" + a + ",b=" + b);
    }

    public static void staticFunction() {
        System.out.println("4");
    }

    int a = 100;
    static int b = 200;

}

嘿嘿,這時(shí)候該怎么打印結(jié)果呢?

按照上面說過的邏輯,應(yīng)該是先靜態(tài)變量和靜態(tài)代碼塊,然后普通成員變量和普通代碼塊,最后是構(gòu)造函數(shù)。

但是因?yàn)殪o態(tài)變量又執(zhí)行了一次new Person(),所以實(shí)例化過程被強(qiáng)行提前了,被嵌入到了初始化過程中,在初始化過程中就進(jìn)行了實(shí)例化。而且因?yàn)槌跏蓟瘺]有結(jié)束就打印了a和b的值,所以static變量b的值就沒有賦值。
這段代碼的結(jié)果就變成了:

2
3
a=100,b=0
1
4

所以,實(shí)例化不一定要在類初始化結(jié)束之后才開始初始化,有可能在初始化過程中就進(jìn)行了實(shí)例化。

另外可以思考下,怎么改就能保證打印的時(shí)候b有值呢?(修改代碼順序或者修改變量修飾符)

類的初始化過程與類的實(shí)例化過程的異同?

學(xué)了上面的內(nèi)容,這個(gè)問題就很簡單了:

  • 類的初始化,是指在類裝載,鏈接之后的一個(gè)階段,會(huì)執(zhí)行<clinit>()方法,初始化靜態(tài)變量,執(zhí)行靜態(tài)代碼塊等。只會(huì)執(zhí)行一次。

  • 類的實(shí)例化,是指在類完全加載到內(nèi)存中后創(chuàng)建對(duì)象的過程,會(huì)執(zhí)行<init>()方法,初始化普通變量,調(diào)用普通代碼塊??梢员徽{(diào)用多次。

一個(gè)實(shí)例變量在對(duì)象初始化的過程中最多可以被賦值幾次?

那我們就試試舉例出最多的情況,其實(shí)也就是每個(gè)要經(jīng)過的地方都對(duì)實(shí)例變量進(jìn)行一次賦值:

  • 1、對(duì)象被創(chuàng)建時(shí)候,分配內(nèi)存會(huì)把實(shí)例變量賦予默認(rèn)值,這是肯定會(huì)發(fā)生的。
  • 2、實(shí)例變量本身初始化的時(shí)候,就給他賦值一次,也就是int value1=100。
  • 3、初始化代碼塊的時(shí)候,也賦值一次。
  • 4、構(gòu)造函數(shù)中,在進(jìn)行賦值一次。

一共四次,看代碼:

public class Person3 {
    public int value1 = 100;

    {
        value1 = 102;
        System.out.println("2");
    }

    public Person3(){
        value1 = 103;
        System.out.println("3");
    }
}

參考

https://blog.csdn.net/justloveyou_/article/details/72466416
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1860
https://www.jianshu.com/p/8a14ed0ed1e9

拜拜

有一起學(xué)習(xí)的小伙伴可以關(guān)注下?? 我的公眾號(hào)——碼上積木,每天剖析一個(gè)知識(shí)點(diǎn),我們一起積累知識(shí)。公眾號(hào)回復(fù)111可獲得面試題《思考與解答》以往期刊。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多