Java類繼承關(guān)系中的初始化順序?qū)嵗斀?/h1>
更新時(shí)間:2019年09月15日 10:55:16 作者:Andy奧
這篇文章主要介紹了Java類繼承關(guān)系中的初始化順序,結(jié)合實(shí)例形式詳細(xì)對(duì)比分析了Java非繼承關(guān)系中的初始化與繼承關(guān)系中的初始化相關(guān)原理與操作技巧,需要的朋友可以參考下
本文實(shí)例講述了Java類繼承關(guān)系中的初始化順序。分享給大家供大家參考,具體如下:
Java類初始化的順序經(jīng)常讓人犯迷糊,現(xiàn)在本文嘗試著從JVM的角度,對(duì)Java非繼承和繼承關(guān)系中類的初始化順序進(jìn)行試驗(yàn),嘗試給出JVM角度的解釋。
非繼承關(guān)系中的初始化順序
對(duì)于非繼承關(guān)系,主類InitialOrderWithoutExtend中包含了靜態(tài)成員變量(類變量)SampleClass 類的一個(gè)實(shí)例,普通成員變量SampleClass 類的2個(gè)實(shí)例(在程序中的順序不一樣)以及一個(gè)靜態(tài)代碼塊,其中靜態(tài)代碼塊中如果靜態(tài)成員變量sam不為空,則改變sam的引用。main()方法中創(chuàng)建了2個(gè)主類對(duì)象,打印2個(gè)主類對(duì)象的靜態(tài)成員sam的屬性s。
代碼1:
package com.j2se;
public class InitialOrderWithoutExtend {
static SampleClass sam = new SampleClass("靜態(tài)成員sam初始化");
SampleClass sam1 = new SampleClass("普通成員sam1初始化");
static {
System.out.println("static塊執(zhí)行");
if (sam == null)
System.out.println("sam is null");
sam = new SampleClass("靜態(tài)塊內(nèi)初始化sam成員變量");
}
SampleClass sam2 = new SampleClass("普通成員sam2初始化");
InitialOrderWithoutExtend() {
System.out.println("InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用");
}
public static void main(String[] args) {
// 創(chuàng)建第1個(gè)主類對(duì)象
System.out.println("第1個(gè)主類對(duì)象:");
InitialOrderWithoutExtend ts = new InitialOrderWithoutExtend();
// 創(chuàng)建第2個(gè)主類對(duì)象
System.out.println("第2個(gè)主類對(duì)象:");
InitialOrderWithoutExtend ts2 = new InitialOrderWithoutExtend();
// 查看兩個(gè)主類對(duì)象的靜態(tài)成員:
System.out.println("2個(gè)主類對(duì)象的靜態(tài)對(duì)象:");
System.out.println("第1個(gè)主類對(duì)象, 靜態(tài)成員sam.s: " + ts.sam);
System.out.println("第2個(gè)主類對(duì)象, 靜態(tài)成員sam.s: " + ts2.sam);
}
}
class SampleClass {
// SampleClass 不能包含任何主類InitialOrderWithoutExtend的成員變量
// 否則導(dǎo)致循環(huán)引用,循環(huán)初始化,調(diào)用棧深度過(guò)大
// 拋出 StackOverFlow 異常
// static InitialOrderWithoutExtend iniClass1 = new InitialOrderWithoutExtend("靜態(tài)成員iniClass1初始化");
// InitialOrderWithoutExtend iniClass2 = new InitialOrderWithoutExtend("普通成員成員iniClass2初始化");
String s;
SampleClass(String s) {
this.s = s;
System.out.println(s);
}
SampleClass() {
System.out.println("SampleClass默認(rèn)構(gòu)造函數(shù)被調(diào)用");
}
@Override
public String toString() {
return this.s;
}
}
輸出結(jié)果:
靜態(tài)成員sam初始化
static塊執(zhí)行
靜態(tài)塊內(nèi)初始化sam成員變量
第1個(gè)主類對(duì)象:
普通成員sam1初始化
普通成員sam2初始化
InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用
第2個(gè)主類對(duì)象:
普通成員sam1初始化
普通成員sam2初始化
InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用
2個(gè)主類對(duì)象的靜態(tài)對(duì)象:
第1個(gè)主類對(duì)象, 靜態(tài)成員sam.s: 靜態(tài)塊內(nèi)初始化sam成員變量
第2個(gè)主類對(duì)象, 靜態(tài)成員sam.s: 靜態(tài)塊內(nèi)初始化sam成員變量
由輸出結(jié)果可知,執(zhí)行順序?yàn)椋?/p>
- static靜態(tài)代碼塊和靜態(tài)成員
- 普通成員
- 構(gòu)造函數(shù)執(zhí)行
當(dāng)具有多個(gè)靜態(tài)成員和靜態(tài)代碼塊或者多個(gè)普通成員時(shí),初始化順序和成員在程序中申明的順序一致。
注意到在該程序的靜態(tài)代碼塊中,修改了靜態(tài)成員sam的引用。main()方法中創(chuàng)建了2個(gè)主類對(duì)象,但是由輸出結(jié)果可知,靜態(tài)成員和靜態(tài)代碼塊只進(jìn)行了一次初始化,并且新建的2個(gè)主類對(duì)象的靜態(tài)成員sam.s是相同的。由此可知,類的靜態(tài)成員和靜態(tài)代碼塊在類加載中是最先進(jìn)行初始化的,并且只進(jìn)行一次。該類的多個(gè)實(shí)例共享靜態(tài)成員,靜態(tài)成員的引用指向程序最后所賦予的引用。
繼承關(guān)系中的初始化順序
此處使用了3個(gè)類來(lái)驗(yàn)證繼承關(guān)系中的初始化順序:Father父類、Son子類和Sample類。父類和子類中各自包含了非靜態(tài)代碼區(qū)、靜態(tài)代碼區(qū)、靜態(tài)成員、普通成員。運(yùn)行時(shí)的主類為InitialOrderWithExtend類,main()方法中創(chuàng)建了一個(gè)子類的對(duì)象,并且使用Father對(duì)象指向Son類實(shí)例的引用(父類對(duì)象指向子類引用,多態(tài))。
代碼2:
package com.j2se;
public class InitialOrderWithExtend {
public static void main(String[] args) {
Father ts = new Son();
}
}
class Father {
{
System.out.println("父類 非靜態(tài)塊 1 執(zhí)行");
}
static {
System.out.println("父類 static塊 1 執(zhí)行");
}
static Sample staticSam1 = new Sample("父類 靜態(tài)成員 staticSam1 初始化");
Sample sam1 = new Sample("父類 普通成員 sam1 初始化");
static Sample staticSam2 = new Sample("父類 靜態(tài)成員 staticSam2 初始化");
static {
System.out.println("父類 static塊 2 執(zhí)行");
}
Father() {
System.out.println("父類 默認(rèn)構(gòu)造函數(shù)被調(diào)用");
}
Sample sam2 = new Sample("父類 普通成員 sam2 初始化");
{
System.out.println("父類 非靜態(tài)塊 2 執(zhí)行");
}
}
class Son extends Father {
{
System.out.println("子類 非靜態(tài)塊 1 執(zhí)行");
}
static Sample staticSamSub1 = new Sample("子類 靜態(tài)成員 staticSamSub1 初始化");
Son() {
System.out.println("子類 默認(rèn)構(gòu)造函數(shù)被調(diào)用");
}
Sample sam1 = new Sample("子類 普通成員 sam1 初始化");
static Sample staticSamSub2 = new Sample("子類 靜態(tài)成員 staticSamSub2 初始化");
static {
System.out.println("子類 static塊1 執(zhí)行");
}
Sample sam2 = new Sample("子類 普通成員 sam2 初始化");
{
System.out.println("子類 非靜態(tài)塊 2 執(zhí)行");
}
static {
System.out.println("子類 static塊2 執(zhí)行");
}
}
class Sample {
Sample(String s) {
System.out.println(s);
}
Sample() {
System.out.println("Sample默認(rèn)構(gòu)造函數(shù)被調(diào)用");
}
}
運(yùn)行結(jié)果:
父類 static塊 1 執(zhí)行
父類 靜態(tài)成員 staticSam1 初始化
父類 靜態(tài)成員 staticSam2 初始化
父類 static塊 2 執(zhí)行
子類 靜態(tài)成員 staticSamSub1 初始化
子類 靜態(tài)成員 staticSamSub2 初始化
子類 static塊1 執(zhí)行
子類 static塊2 執(zhí)行
父類 非靜態(tài)塊 1 執(zhí)行
父類 普通成員 sam1 初始化
父類 普通成員 sam2 初始化
父類 非靜態(tài)塊 2 執(zhí)行
父類 默認(rèn)構(gòu)造函數(shù)被調(diào)用
子類 非靜態(tài)塊 1 執(zhí)行
子類 普通成員 sam1 初始化
子類 普通成員 sam2 初始化
子類 非靜態(tài)塊 2 執(zhí)行
子類 默認(rèn)構(gòu)造函數(shù)被調(diào)用
由輸出結(jié)果可知,執(zhí)行的順序?yàn)椋?/p>
- 父類靜態(tài)代碼區(qū)和父類靜態(tài)成員
- 子類靜態(tài)代碼區(qū)和子類靜態(tài)成員
- 父類非靜態(tài)代碼區(qū)和普通成員
- 父類構(gòu)造函數(shù)
- 子類非靜態(tài)代碼區(qū)和普通成員
- 子類構(gòu)造函數(shù)
與非繼承關(guān)系中的初始化順序一致的地方在于,靜態(tài)代碼區(qū)和父類靜態(tài)成員、非靜態(tài)代碼區(qū)和普通成員是同一級(jí)別的,當(dāng)存在多個(gè)這樣的代碼塊或者成員時(shí),初始化的順序和它們?cè)诔绦蛑猩昝鞯捻樞蛞恢拢淮送?,靜態(tài)代碼區(qū)和靜態(tài)成員也是僅僅初始化一次,但是在初始化過(guò)程中,可以修改靜態(tài)成員的引用。
初始化順序圖示
非繼承關(guān)系

繼承關(guān)系

類初始化順序的JVM解釋
類初始化順序受到JVM類加載機(jī)制的控制,類加載機(jī)制包括加載、驗(yàn)證、準(zhǔn)備、解析、初始化等步驟。不管是在繼承還是非繼承關(guān)系中,類的初始化順序主要受到JVM類加載時(shí)機(jī)、解析和clinit()初始化規(guī)則的影響。
加載時(shí)機(jī)
加載是類加載機(jī)制的第一個(gè)階段,只有在5種主動(dòng)引用的情況下,才會(huì)觸發(fā)類的加載,而在其他被動(dòng)引用的情況下并不會(huì)觸發(fā)類的加載。關(guān)于類加載時(shí)機(jī)和5中主動(dòng)引用和被動(dòng)引用詳見(jiàn)【深入理解JVM】:類加載機(jī)制。其中3種主動(dòng)引用的形式為:
- 程序啟動(dòng)需要觸發(fā)main方法的時(shí)候,虛擬機(jī)會(huì)先觸發(fā)這個(gè)類的初始化
- 使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、JIT時(shí)放入常量池的靜態(tài)字段除外)、調(diào)用一個(gè)類的靜態(tài)方法,會(huì)觸發(fā)初始化
- 當(dāng)初始化一個(gè)類的時(shí)候,如果其父類沒(méi)有初始化,則需要先觸發(fā)其父類的初始化
代碼1中觸發(fā)main()方法前,需要觸發(fā)主類InitialOrderWithoutExtend的初始化,主類初始化觸發(fā)后,對(duì)靜態(tài)代碼區(qū)和靜態(tài)成員進(jìn)行初始化后,打印”第1個(gè)主類對(duì)象:”,之后遇到newInitialOrderWithoutExtend ts = new InitialOrderWithoutExtend();
,再進(jìn)行其他普通變量的初始化。
代碼2是繼承關(guān)系,在子類初始化前,必須先觸發(fā)父類的初始化。
類解析在繼承關(guān)系中的自下而上遞歸
類加載機(jī)制的解析階段將常量池中的符號(hào)引用替換為直接引用,主要針對(duì)的是類或者接口、字段、類方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用。關(guān)于類的解析過(guò)程詳見(jiàn)【深入理解JVM】:類加載機(jī)制。
而在字段解析、類方法解析、方法類型解析中,均遵循繼承關(guān)系中自下而上遞歸搜索解析的規(guī)則,由于遞歸的特性(即數(shù)據(jù)結(jié)構(gòu)中棧的“后進(jìn)先出”),初始化的過(guò)程則是由上而下、從父類到子類的初始化順序。
初始化clinit()方法
初始化階段是執(zhí)行類構(gòu)造器方法clinit() 的過(guò)程。clinit() 是編譯器自動(dòng)收集類中所有類變量(靜態(tài)變量)的賦值動(dòng)作和靜態(tài)語(yǔ)句塊合并生成的。編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序決定的。JVM會(huì)保證在子類的clinit() 方法執(zhí)行之前,父類的clinit() 方法已經(jīng)執(zhí)行完畢。
因此所有的初始化過(guò)程中clinit()方法,保證了靜態(tài)變量和靜態(tài)語(yǔ)句塊總是最先初始化的,并且一定是先執(zhí)行父類clinit(),在執(zhí)行子類的clinit()。
代碼順序與對(duì)象內(nèi)存布局
在前面的分析中我們看到,類的初始化具有相對(duì)固定的順序:靜態(tài)代碼區(qū)和靜態(tài)變量先于非靜態(tài)代碼區(qū)和普通成員,先于構(gòu)造函數(shù)。在相同級(jí)別的初始化過(guò)程中,初始化順序與變量定義在程序的中順序是一致的。
而代碼順序在對(duì)象內(nèi)存布局中同樣有影響。(關(guān)于JVM對(duì)象內(nèi)存布局詳見(jiàn)【深入理解JVM】:Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位。)
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。而實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中所定義的各種類型的字段內(nèi)容。
無(wú)論是從父類繼承還是子類定義的,都需要記錄下來(lái),這部分的存儲(chǔ)順序JVM參數(shù)和字段在程序源碼中定義順序的影響。HotSpot虛擬機(jī)默認(rèn)的分配策略為longs/doubles、ints、shorts/chars、bytes/booleans、oop,從分配策略中可以看出,相同寬度的字段總是分配到一起。滿足這個(gè)條件的前提下,父類中定義的變量會(huì)出現(xiàn)在子類之前。不過(guò),如果啟用了JVM參數(shù)CompactFields(默認(rèn)為true,啟用),那么子類中較窄的變量也可能會(huì)插入到父類變量的空隙中。
更多java相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Java面向?qū)ο蟪绦蛟O(shè)計(jì)入門(mén)與進(jìn)階教程》、《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java操作DOM節(jié)點(diǎn)技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對(duì)大家java程序設(shè)計(jì)有所幫助。
相關(guān)文章
-
一鍵清除maven倉(cāng)庫(kù)中下載失敗的jar包的實(shí)現(xiàn)方法
這篇文章主要介紹了一鍵清除maven倉(cāng)庫(kù)中下載失敗的jar包的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧 2020-07-07
-
Java Web Filter 過(guò)濾器學(xué)習(xí)教程(推薦)
Filter也稱之為過(guò)濾器,它是Servlet技術(shù)中最激動(dòng)人心的技術(shù).這篇文章主要介紹了Java Web Filter 過(guò)濾器學(xué)習(xí)教程的相關(guān)資料,需要的朋友可以參考下 2016-05-05
-
詳解jenkins自動(dòng)部署springboot應(yīng)用的方法
這篇文章主要介紹了詳解jenkins自動(dòng)部署springboot應(yīng)用的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧 2018-08-08
-
idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用小結(jié)
Spring Boot是Spring社區(qū)發(fā)布的一個(gè)開(kāi)源項(xiàng)目,旨在幫助開(kāi)發(fā)者快速并且更簡(jiǎn)單的構(gòu)建項(xiàng)目,Spring Boot框架,其功能非常簡(jiǎn)單,便是幫助我們實(shí)現(xiàn)自動(dòng)配置,本文給大家介紹idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用,感興趣的朋友跟隨小編一起看看吧 2023-11-11
-
子線程任務(wù)發(fā)生異常時(shí)主線程事務(wù)回滾示例過(guò)程
這篇文章主要為大家介紹了子線程任務(wù)發(fā)生了異常時(shí)主線程事務(wù)如何回滾的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步 2022-03-03
-
詳解java解決分布式環(huán)境中高并發(fā)環(huán)境下數(shù)據(jù)插入重復(fù)問(wèn)題
這篇文章主要介紹了java解決并發(fā)數(shù)據(jù)重復(fù)問(wèn)題 ,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
2019-03-03
-
jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA)
這篇文章主要介紹了jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下 2020-11-11
最新評(píng)論
本文實(shí)例講述了Java類繼承關(guān)系中的初始化順序。分享給大家供大家參考,具體如下:
Java類初始化的順序經(jīng)常讓人犯迷糊,現(xiàn)在本文嘗試著從JVM的角度,對(duì)Java非繼承和繼承關(guān)系中類的初始化順序進(jìn)行試驗(yàn),嘗試給出JVM角度的解釋。
非繼承關(guān)系中的初始化順序
對(duì)于非繼承關(guān)系,主類InitialOrderWithoutExtend中包含了靜態(tài)成員變量(類變量)SampleClass 類的一個(gè)實(shí)例,普通成員變量SampleClass 類的2個(gè)實(shí)例(在程序中的順序不一樣)以及一個(gè)靜態(tài)代碼塊,其中靜態(tài)代碼塊中如果靜態(tài)成員變量sam不為空,則改變sam的引用。main()方法中創(chuàng)建了2個(gè)主類對(duì)象,打印2個(gè)主類對(duì)象的靜態(tài)成員sam的屬性s。
代碼1:
package com.j2se; public class InitialOrderWithoutExtend { static SampleClass sam = new SampleClass("靜態(tài)成員sam初始化"); SampleClass sam1 = new SampleClass("普通成員sam1初始化"); static { System.out.println("static塊執(zhí)行"); if (sam == null) System.out.println("sam is null"); sam = new SampleClass("靜態(tài)塊內(nèi)初始化sam成員變量"); } SampleClass sam2 = new SampleClass("普通成員sam2初始化"); InitialOrderWithoutExtend() { System.out.println("InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用"); } public static void main(String[] args) { // 創(chuàng)建第1個(gè)主類對(duì)象 System.out.println("第1個(gè)主類對(duì)象:"); InitialOrderWithoutExtend ts = new InitialOrderWithoutExtend(); // 創(chuàng)建第2個(gè)主類對(duì)象 System.out.println("第2個(gè)主類對(duì)象:"); InitialOrderWithoutExtend ts2 = new InitialOrderWithoutExtend(); // 查看兩個(gè)主類對(duì)象的靜態(tài)成員: System.out.println("2個(gè)主類對(duì)象的靜態(tài)對(duì)象:"); System.out.println("第1個(gè)主類對(duì)象, 靜態(tài)成員sam.s: " + ts.sam); System.out.println("第2個(gè)主類對(duì)象, 靜態(tài)成員sam.s: " + ts2.sam); } } class SampleClass { // SampleClass 不能包含任何主類InitialOrderWithoutExtend的成員變量 // 否則導(dǎo)致循環(huán)引用,循環(huán)初始化,調(diào)用棧深度過(guò)大 // 拋出 StackOverFlow 異常 // static InitialOrderWithoutExtend iniClass1 = new InitialOrderWithoutExtend("靜態(tài)成員iniClass1初始化"); // InitialOrderWithoutExtend iniClass2 = new InitialOrderWithoutExtend("普通成員成員iniClass2初始化"); String s; SampleClass(String s) { this.s = s; System.out.println(s); } SampleClass() { System.out.println("SampleClass默認(rèn)構(gòu)造函數(shù)被調(diào)用"); } @Override public String toString() { return this.s; } }
輸出結(jié)果:
靜態(tài)成員sam初始化 static塊執(zhí)行 靜態(tài)塊內(nèi)初始化sam成員變量 第1個(gè)主類對(duì)象: 普通成員sam1初始化 普通成員sam2初始化 InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用 第2個(gè)主類對(duì)象: 普通成員sam1初始化 普通成員sam2初始化 InitialOrderWithoutExtend默認(rèn)構(gòu)造函數(shù)被調(diào)用 2個(gè)主類對(duì)象的靜態(tài)對(duì)象: 第1個(gè)主類對(duì)象, 靜態(tài)成員sam.s: 靜態(tài)塊內(nèi)初始化sam成員變量 第2個(gè)主類對(duì)象, 靜態(tài)成員sam.s: 靜態(tài)塊內(nèi)初始化sam成員變量
由輸出結(jié)果可知,執(zhí)行順序?yàn)椋?/p>
- static靜態(tài)代碼塊和靜態(tài)成員
- 普通成員
- 構(gòu)造函數(shù)執(zhí)行
當(dāng)具有多個(gè)靜態(tài)成員和靜態(tài)代碼塊或者多個(gè)普通成員時(shí),初始化順序和成員在程序中申明的順序一致。
注意到在該程序的靜態(tài)代碼塊中,修改了靜態(tài)成員sam的引用。main()方法中創(chuàng)建了2個(gè)主類對(duì)象,但是由輸出結(jié)果可知,靜態(tài)成員和靜態(tài)代碼塊只進(jìn)行了一次初始化,并且新建的2個(gè)主類對(duì)象的靜態(tài)成員sam.s是相同的。由此可知,類的靜態(tài)成員和靜態(tài)代碼塊在類加載中是最先進(jìn)行初始化的,并且只進(jìn)行一次。該類的多個(gè)實(shí)例共享靜態(tài)成員,靜態(tài)成員的引用指向程序最后所賦予的引用。
繼承關(guān)系中的初始化順序
此處使用了3個(gè)類來(lái)驗(yàn)證繼承關(guān)系中的初始化順序:Father父類、Son子類和Sample類。父類和子類中各自包含了非靜態(tài)代碼區(qū)、靜態(tài)代碼區(qū)、靜態(tài)成員、普通成員。運(yùn)行時(shí)的主類為InitialOrderWithExtend類,main()方法中創(chuàng)建了一個(gè)子類的對(duì)象,并且使用Father對(duì)象指向Son類實(shí)例的引用(父類對(duì)象指向子類引用,多態(tài))。
代碼2:
package com.j2se; public class InitialOrderWithExtend { public static void main(String[] args) { Father ts = new Son(); } } class Father { { System.out.println("父類 非靜態(tài)塊 1 執(zhí)行"); } static { System.out.println("父類 static塊 1 執(zhí)行"); } static Sample staticSam1 = new Sample("父類 靜態(tài)成員 staticSam1 初始化"); Sample sam1 = new Sample("父類 普通成員 sam1 初始化"); static Sample staticSam2 = new Sample("父類 靜態(tài)成員 staticSam2 初始化"); static { System.out.println("父類 static塊 2 執(zhí)行"); } Father() { System.out.println("父類 默認(rèn)構(gòu)造函數(shù)被調(diào)用"); } Sample sam2 = new Sample("父類 普通成員 sam2 初始化"); { System.out.println("父類 非靜態(tài)塊 2 執(zhí)行"); } } class Son extends Father { { System.out.println("子類 非靜態(tài)塊 1 執(zhí)行"); } static Sample staticSamSub1 = new Sample("子類 靜態(tài)成員 staticSamSub1 初始化"); Son() { System.out.println("子類 默認(rèn)構(gòu)造函數(shù)被調(diào)用"); } Sample sam1 = new Sample("子類 普通成員 sam1 初始化"); static Sample staticSamSub2 = new Sample("子類 靜態(tài)成員 staticSamSub2 初始化"); static { System.out.println("子類 static塊1 執(zhí)行"); } Sample sam2 = new Sample("子類 普通成員 sam2 初始化"); { System.out.println("子類 非靜態(tài)塊 2 執(zhí)行"); } static { System.out.println("子類 static塊2 執(zhí)行"); } } class Sample { Sample(String s) { System.out.println(s); } Sample() { System.out.println("Sample默認(rèn)構(gòu)造函數(shù)被調(diào)用"); } }
運(yùn)行結(jié)果:
父類 static塊 1 執(zhí)行 父類 靜態(tài)成員 staticSam1 初始化 父類 靜態(tài)成員 staticSam2 初始化 父類 static塊 2 執(zhí)行 子類 靜態(tài)成員 staticSamSub1 初始化 子類 靜態(tài)成員 staticSamSub2 初始化 子類 static塊1 執(zhí)行 子類 static塊2 執(zhí)行 父類 非靜態(tài)塊 1 執(zhí)行 父類 普通成員 sam1 初始化 父類 普通成員 sam2 初始化 父類 非靜態(tài)塊 2 執(zhí)行 父類 默認(rèn)構(gòu)造函數(shù)被調(diào)用 子類 非靜態(tài)塊 1 執(zhí)行 子類 普通成員 sam1 初始化 子類 普通成員 sam2 初始化 子類 非靜態(tài)塊 2 執(zhí)行 子類 默認(rèn)構(gòu)造函數(shù)被調(diào)用
由輸出結(jié)果可知,執(zhí)行的順序?yàn)椋?/p>
- 父類靜態(tài)代碼區(qū)和父類靜態(tài)成員
- 子類靜態(tài)代碼區(qū)和子類靜態(tài)成員
- 父類非靜態(tài)代碼區(qū)和普通成員
- 父類構(gòu)造函數(shù)
- 子類非靜態(tài)代碼區(qū)和普通成員
- 子類構(gòu)造函數(shù)
與非繼承關(guān)系中的初始化順序一致的地方在于,靜態(tài)代碼區(qū)和父類靜態(tài)成員、非靜態(tài)代碼區(qū)和普通成員是同一級(jí)別的,當(dāng)存在多個(gè)這樣的代碼塊或者成員時(shí),初始化的順序和它們?cè)诔绦蛑猩昝鞯捻樞蛞恢拢淮送?,靜態(tài)代碼區(qū)和靜態(tài)成員也是僅僅初始化一次,但是在初始化過(guò)程中,可以修改靜態(tài)成員的引用。
初始化順序圖示
非繼承關(guān)系
繼承關(guān)系
類初始化順序的JVM解釋
類初始化順序受到JVM類加載機(jī)制的控制,類加載機(jī)制包括加載、驗(yàn)證、準(zhǔn)備、解析、初始化等步驟。不管是在繼承還是非繼承關(guān)系中,類的初始化順序主要受到JVM類加載時(shí)機(jī)、解析和clinit()初始化規(guī)則的影響。
加載時(shí)機(jī)
加載是類加載機(jī)制的第一個(gè)階段,只有在5種主動(dòng)引用的情況下,才會(huì)觸發(fā)類的加載,而在其他被動(dòng)引用的情況下并不會(huì)觸發(fā)類的加載。關(guān)于類加載時(shí)機(jī)和5中主動(dòng)引用和被動(dòng)引用詳見(jiàn)【深入理解JVM】:類加載機(jī)制。其中3種主動(dòng)引用的形式為:
- 程序啟動(dòng)需要觸發(fā)main方法的時(shí)候,虛擬機(jī)會(huì)先觸發(fā)這個(gè)類的初始化
- 使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、JIT時(shí)放入常量池的靜態(tài)字段除外)、調(diào)用一個(gè)類的靜態(tài)方法,會(huì)觸發(fā)初始化
- 當(dāng)初始化一個(gè)類的時(shí)候,如果其父類沒(méi)有初始化,則需要先觸發(fā)其父類的初始化
代碼1中觸發(fā)main()方法前,需要觸發(fā)主類InitialOrderWithoutExtend的初始化,主類初始化觸發(fā)后,對(duì)靜態(tài)代碼區(qū)和靜態(tài)成員進(jìn)行初始化后,打印”第1個(gè)主類對(duì)象:”,之后遇到newInitialOrderWithoutExtend ts = new InitialOrderWithoutExtend();
,再進(jìn)行其他普通變量的初始化。
代碼2是繼承關(guān)系,在子類初始化前,必須先觸發(fā)父類的初始化。
類解析在繼承關(guān)系中的自下而上遞歸
類加載機(jī)制的解析階段將常量池中的符號(hào)引用替換為直接引用,主要針對(duì)的是類或者接口、字段、類方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用。關(guān)于類的解析過(guò)程詳見(jiàn)【深入理解JVM】:類加載機(jī)制。
而在字段解析、類方法解析、方法類型解析中,均遵循繼承關(guān)系中自下而上遞歸搜索解析的規(guī)則,由于遞歸的特性(即數(shù)據(jù)結(jié)構(gòu)中棧的“后進(jìn)先出”),初始化的過(guò)程則是由上而下、從父類到子類的初始化順序。
初始化clinit()方法
初始化階段是執(zhí)行類構(gòu)造器方法clinit() 的過(guò)程。clinit() 是編譯器自動(dòng)收集類中所有類變量(靜態(tài)變量)的賦值動(dòng)作和靜態(tài)語(yǔ)句塊合并生成的。編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序決定的。JVM會(huì)保證在子類的clinit() 方法執(zhí)行之前,父類的clinit() 方法已經(jīng)執(zhí)行完畢。
因此所有的初始化過(guò)程中clinit()方法,保證了靜態(tài)變量和靜態(tài)語(yǔ)句塊總是最先初始化的,并且一定是先執(zhí)行父類clinit(),在執(zhí)行子類的clinit()。
代碼順序與對(duì)象內(nèi)存布局
在前面的分析中我們看到,類的初始化具有相對(duì)固定的順序:靜態(tài)代碼區(qū)和靜態(tài)變量先于非靜態(tài)代碼區(qū)和普通成員,先于構(gòu)造函數(shù)。在相同級(jí)別的初始化過(guò)程中,初始化順序與變量定義在程序的中順序是一致的。
而代碼順序在對(duì)象內(nèi)存布局中同樣有影響。(關(guān)于JVM對(duì)象內(nèi)存布局詳見(jiàn)【深入理解JVM】:Java對(duì)象的創(chuàng)建、內(nèi)存布局、訪問(wèn)定位。)
在HotSpot虛擬機(jī)中,對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為3塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。而實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息,也是程序代碼中所定義的各種類型的字段內(nèi)容。
無(wú)論是從父類繼承還是子類定義的,都需要記錄下來(lái),這部分的存儲(chǔ)順序JVM參數(shù)和字段在程序源碼中定義順序的影響。HotSpot虛擬機(jī)默認(rèn)的分配策略為longs/doubles、ints、shorts/chars、bytes/booleans、oop,從分配策略中可以看出,相同寬度的字段總是分配到一起。滿足這個(gè)條件的前提下,父類中定義的變量會(huì)出現(xiàn)在子類之前。不過(guò),如果啟用了JVM參數(shù)CompactFields(默認(rèn)為true,啟用),那么子類中較窄的變量也可能會(huì)插入到父類變量的空隙中。
更多java相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Java面向?qū)ο蟪绦蛟O(shè)計(jì)入門(mén)與進(jìn)階教程》、《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java操作DOM節(jié)點(diǎn)技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對(duì)大家java程序設(shè)計(jì)有所幫助。
相關(guān)文章
一鍵清除maven倉(cāng)庫(kù)中下載失敗的jar包的實(shí)現(xiàn)方法
這篇文章主要介紹了一鍵清除maven倉(cāng)庫(kù)中下載失敗的jar包的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Java Web Filter 過(guò)濾器學(xué)習(xí)教程(推薦)
Filter也稱之為過(guò)濾器,它是Servlet技術(shù)中最激動(dòng)人心的技術(shù).這篇文章主要介紹了Java Web Filter 過(guò)濾器學(xué)習(xí)教程的相關(guān)資料,需要的朋友可以參考下2016-05-05詳解jenkins自動(dòng)部署springboot應(yīng)用的方法
這篇文章主要介紹了詳解jenkins自動(dòng)部署springboot應(yīng)用的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用小結(jié)
Spring Boot是Spring社區(qū)發(fā)布的一個(gè)開(kāi)源項(xiàng)目,旨在幫助開(kāi)發(fā)者快速并且更簡(jiǎn)單的構(gòu)建項(xiàng)目,Spring Boot框架,其功能非常簡(jiǎn)單,便是幫助我們實(shí)現(xiàn)自動(dòng)配置,本文給大家介紹idea創(chuàng)建SpringBoot項(xiàng)目及注解配置相關(guān)應(yīng)用,感興趣的朋友跟隨小編一起看看吧2023-11-11子線程任務(wù)發(fā)生異常時(shí)主線程事務(wù)回滾示例過(guò)程
這篇文章主要為大家介紹了子線程任務(wù)發(fā)生了異常時(shí)主線程事務(wù)如何回滾的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03詳解java解決分布式環(huán)境中高并發(fā)環(huán)境下數(shù)據(jù)插入重復(fù)問(wèn)題
這篇文章主要介紹了java解決并發(fā)數(shù)據(jù)重復(fù)問(wèn)題 ,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA)
這篇文章主要介紹了jmeter添加自定函數(shù)的實(shí)例(jmeter5.3+IntelliJ IDEA),本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11