Java中類的初始化和實例化區(qū)別詳解
一、區(qū)別
類的初始化:
是完成程序執(zhí)行前的準備工作。
在這個階段,靜態(tài)的(變量,方法,代碼塊)會被執(zhí)行。同時在會開辟一塊存儲空間用來存放靜態(tài)的數(shù)據(jù)。
初始化只在類加載的時候執(zhí)行一次
類的實例化(實例化對象):
是指創(chuàng)建一個對象的過程。這個過程中會在堆中開辟內(nèi)存,將一些非靜態(tài)的方法,變量存放在里面。在程序執(zhí)行的過程中,可以創(chuàng)建多個對象,既多次實例化。每次實例化都會開辟一塊新的內(nèi)存。(就是調(diào)用構(gòu)造函數(shù))
在每個類初始化使用前,都會先對該類進行加載(不是指類的實例化)
類加載有幾個步驟,加載->驗證->準備->解析->初始化
在編譯過程會把常量的值放入類的常量池中,在準備過程會對類變量(static修飾的變量)賦初始值,也就是零值,同時會將常量的值賦予常量;在初始化過程會按照類文件中的聲明順序執(zhí)行類變量的賦值和靜態(tài)語句塊(static{}塊),如果父類還沒有初始化會先進行父類的初始化,完成后才會進行子類的初始化。
可以看到在初始化階段就會執(zhí)行static{}塊的語句,而每一個類在運行過程中一般只會被加載一次(不是指實例化,特殊情況是使用其他類加載器對類進行加載),所以只會完成一次初始化過程,因此也就只會執(zhí)行static{}塊一次。 類的實例化和類的加載是兩個不同的概念。
1.主要區(qū)別
2.基礎(chǔ)知識
2.1 java類的生命周期:
指一個class文件從加載到卸載的全過程,類的完整生命周期包括7個部分:加載——驗證——準備——解析——初始化——使用——卸載,如下圖所示
其中,驗證——準備——解析 稱為連接階段,除了解析外,其他階段是順序發(fā)生的,而解析可以與這些階段交叉進行,因為Java支持動態(tài)綁定(晚期綁定),需要運行時才能確定具體類型;在使用階段實例化對象
2.2 類加載過程:
- 加載:通過類名獲取類的二進制字節(jié)流是通過類加載器來完成的。其加載過程使用“雙親委派模型”
- 驗證:當一個類被加載之后,必須要驗證一下這個類是否合法,比如這個類是不是符合字節(jié)碼的格式、變量與方法是不是有重復、數(shù)據(jù)類型是不是有效、繼承與實現(xiàn)是否合乎標準等等。總之,這個階段的目的就是保證加載的類是能夠被jvm所運行。
- 準備:為類變量(靜態(tài)變量)在方法區(qū)分配內(nèi)存,并設(shè)置零值。注意:這里是類變量,不是實例變量,實例變量是對象分配到堆內(nèi)存時根據(jù)運行時動態(tài)生成的。
- 解析:把常量池中的符號引用解析為直接引用:根據(jù)符號引用所作的描述,在內(nèi)存中找到符合描述的目標并把目標指針指針返回。
- 初始化:類的初始化過程是這樣的:按照順序自上而下運行類中的變量賦值語句和靜態(tài)語句,如果有父類,則首先按照順序運行父類中的變量賦值語句和靜態(tài)語句。在類的初始化階段,只會初始化與類相關(guān)的靜態(tài)賦值語句和靜態(tài)語句,也就是有static關(guān)鍵字修飾的信息,而沒有static修飾的賦值語句和執(zhí)行語句在實例化對象的時候才會運行。執(zhí)行<clinit>()方法(clinit是class initialize的簡寫)
- 實例化:在堆區(qū)分配內(nèi)存空間,執(zhí)行實例對象初始化,設(shè)置引用變量a指向剛分配的內(nèi)存地址
二、案例詳解
1、示例1
首先來看一個最簡單的例子(無父類且無靜態(tài)成員變量)
public class OrderOfInitialization1 { public static void main(String[] args) { House house = new House(); house.f(); } } class Window { Window(int market) { System.out.println("Window(" + market + ")"); } } class House { Window w1 = new Window(1);// before constructor House() { // show we're in constructor System.out.println("House()"); w3 = new Window(33);//reinitialize w3 } Window w2 = new Window(2);// after constructor void f() { System.out.println("f()"); } Window w3 = new Window(3);//at end }
output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
從輸出結(jié)果分析,House 實例變量的初始化順序是: w1
, w2
, w3
,然后才是構(gòu)造函數(shù)。
即:實例變量按照其在代碼中出現(xiàn)的順序執(zhí)行初始化,然后執(zhí)行構(gòu)造函數(shù)里面的初始化。
2、示例2
接下來看一個稍微復雜一些的例子(有父類但無靜態(tài)成員變量)
public class OrderOfInitialization2 { public static void main(String[] args) { SubClass subClass = new SubClass(); } } class Print { Print(int i) { System.out.println("new Print(" + i + ")"); } } class SuperClass { Print print1 = new Print(1); public SuperClass() { System.out.println("new SuperClass()"); } Print print2 = new Print(2); } class SubClass extends SuperClass { Print print3 = new Print(3); public SubClass() { //這個地方其實是調(diào)用了父類的默認的無參構(gòu)造函數(shù),super(); //如果父類沒有無參構(gòu)造函數(shù),則這個地方必須顯式的調(diào)用父類的構(gòu)造函數(shù),否則編譯不通過 System.out.println("new SubClass()"); } Print print4 = new Print(4); }
output:
new Print(1)
new Print(2)
new SuperClass()
new Print(3)
new Print(4)
new SubClass()
從輸出結(jié)果分析:這個地方是先調(diào)用了父類 SuperClass 的構(gòu)造函數(shù),然后調(diào)用子類 SubClass 的構(gòu)造函數(shù)。
即:如果一個類有父類,在實例化子類的時候,會先執(zhí)行父類的構(gòu)造函數(shù),然后執(zhí)行子類的構(gòu)造函數(shù)。
3、示例3
繼續(xù)看一個更復雜一些的例子(有父類且有靜態(tài)成員變量)
public class OrderOfInitialization3 { public static void main(String[] args) { Man man = new Man(); Man man1 = new Man(); } static Print1 print0 = new Print1(0); } class Print1 { Print1(int i) { System.out.println("new Print1(" + i + ")"); } } class People { Print1 print1 = new Print1(1); public People() { System.out.println("new People()"); } Print1 print2 = new Print1(2); static Print1 print5 = new Print1(5); } class Man extends People { Print1 print3 = new Print1(3); public Man() { System.out.println("new Man()"); } Print1 print4 = new Print1(4); static Print1 print6 = new Print1(6); }
output:
new Print(0)
new Print(5)
new Print(6)
new Print(1)
new Print(2)
new People()
new Print(3)
new Print(4)
new Man()
new Print(1)
new Print(2)
new People()
new Print(3)
new Print(4)
new Man()
從輸出結(jié)果分析:這里首先執(zhí)行了 OrderOfInitialization3 類的靜態(tài)變量 print0 的初始化(輸出 new Print(0)),然后執(zhí)行靜態(tài)方法 main;
緊接著是執(zhí)行 People 類的靜態(tài)成員變量 print5 的初始化(輸出 new Print(5)),再接著是 Man 類的靜態(tài)成員變量 print6 的初始化(輸出 new Print(6));
之后是 People 的實例變量(輸出new Print(1)、new Print(2))、構(gòu)造函數(shù)(輸出 new People())初始化
最后才是 Man 實例變量(輸出 new Print(3)、new Print(4))、構(gòu)造函數(shù)(輸出 new Man())的初始化。
在第二次實例化一個 Man 的時候,所有的靜態(tài)成員變量都沒有相應(yīng)的輸出,即靜態(tài)成員變量只初始化了一次。
所以這個地方執(zhí)行的順序是:首先執(zhí)行 main 所在類的靜態(tài)成員變量的初始化,然后是 Man 的父類的靜態(tài)成員變量的初始化,然后是子類的靜態(tài)成員的初始化;
接著是父類的構(gòu)造函數(shù),最后才是子類的構(gòu)造函數(shù)。
這個地方 Man 實例化了兩次,但是其父類和本身的靜態(tài)成員變量只初始化了一次。
為什么靜態(tài)成員變量只會初始化一次呢?
- 實際上,靜態(tài)成員變量初始化的過程本質(zhì)上就是一個類的加載和初始化的過程,虛擬機保證了在同一個類加載器下,一個類型只會初始化一次。
4、總結(jié)一下
這個地方把類和對象分開會更好理解一點。
(1)類的初始化
- 靜態(tài)成員變量初始化發(fā)生在靜態(tài)方法之前
- 父類的初始化必須在子類初始化之前
- 靜態(tài)成員變量的初始化順序為其在代碼中出現(xiàn)的順序
(2)實例化對象
- 如果有父類,先執(zhí)行父類的實例化
- 成員變量初始化發(fā)生在構(gòu)造函數(shù)之前
- 成員變量的初始化順序為其在代碼中出現(xiàn)的順序
(3)實例化對象之前如果該類沒有初始化,必須先執(zhí)行該類的初始化。
5、最后看一個比較特殊的例子
public class NestedInitialization { public static void main(String[] args) { staticFunction(); } static NestedInitialization st = new NestedInitialization(); static { System.out.println("1"); } { System.out.println("2"); } NestedInitialization() { System.out.println("3"); System.out.println("a=" + a + ",b=" + b); } public static void staticFunction() { System.out.println("4"); } int a = 110; static int b = 112; }
output:
2
3
a=110,b=0
1
4
這個例子的特殊性在于,**該類還沒有完成初始化,就去實例化一個該類的對象。**我們從類的生命周期來分析,一個類會經(jīng)歷加載、驗證、準備、解析、初始化、使用和卸載七個階段,在執(zhí)行到 static NestedInitialization st = new NestedInitialization();
這一步時,已經(jīng)是類的初始化階段了,此時,NestedInitialization 類里面的靜態(tài)成員的值都還是準備階段設(shè)置的初始零值,即 static NestedInitialization st = null
, static int b = 0;
,然后這個地方需要實例化 NestedInitialization,所以是以此時的狀態(tài)去實例化 NestedInitialization 類的,執(zhí)行類的實例化,然后在繼續(xù)類的初始化。所以才會出現(xiàn)上面的輸出結(jié)果。
到此這篇關(guān)于Java中類的初始化和實例化區(qū)別詳解的文章就介紹到這了,更多相關(guān)Java初始化和實例化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java在Excel中創(chuàng)建多級分組、折疊或展開分組的實現(xiàn)
這篇文章主要介紹了Java在Excel中創(chuàng)建多級分組、折疊或展開分組的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-05-05基于指針pointers和引用references的區(qū)別分析
本篇文章介紹了,基于指針pointers和引用references的區(qū)別分析。需要的朋友參考下2013-05-05IDEA2020.1構(gòu)建Spring5.2.x源碼的方法
這篇文章主要介紹了IDEA2020.1構(gòu)建Spring5.2.x源碼的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10