摘要:
?在Java中糖驴,一個(gè)對(duì)象在可以被使用之前必須要被正確地初始化霉猛,這一點(diǎn)是Java規(guī)范規(guī)定的。在實(shí)例化一個(gè)對(duì)象時(shí)酷师,JVM首先會(huì)檢查相關(guān)類型是否已經(jīng)加載并初始化讶凉,如果沒(méi)有,則JVM立即進(jìn)行加載并調(diào)用類構(gòu)造器完成類的初始化懂讯。在類初始化過(guò)程中或初始化完畢后譬挚,根據(jù)具體情況才會(huì)去對(duì)類進(jìn)行實(shí)例化漆腌。本文試圖對(duì)JVM執(zhí)行類初始化和實(shí)例化的過(guò)程做一個(gè)詳細(xì)深入地介紹女坑,以便從Java虛擬機(jī)的角度清晰解剖一個(gè)Java對(duì)象的創(chuàng)建過(guò)程碉就。
友情提示:
?一個(gè)Java對(duì)象的創(chuàng)建過(guò)程往往包括類初始化 和 類實(shí)例化 兩個(gè)階段。在《JVM類加載機(jī)制》主要介紹了類的初始化時(shí)機(jī)和初始化過(guò)程呀酸,本文在此基礎(chǔ)上爆存,進(jìn)一步闡述了一個(gè)Java對(duì)象創(chuàng)建的真實(shí)過(guò)程闲勺。
一、Java對(duì)象創(chuàng)建時(shí)機(jī)
?我們知道扣猫,一個(gè)對(duì)象在可以被使用之前必須要被正確地實(shí)例化菜循。在Java代碼中,有很多行為可以引起對(duì)象的創(chuàng)建申尤,最為直觀的一種就是使用new關(guān)鍵字來(lái)調(diào)用一個(gè)類的構(gòu)造函數(shù)顯式地創(chuàng)建對(duì)象癌幕,這種方式在Java規(guī)范中被稱為 : 由執(zhí)行類實(shí)例創(chuàng)建表達(dá)式而引起的對(duì)象創(chuàng)建。除此之外昧穿,我們還可以使用反射機(jī)制(Class類的newInstance方法勺远、使用Constructor類的newInstance方法)、使用Clone方法时鸵、使用反序列化等方式創(chuàng)建對(duì)象胶逢。下面筆者分別對(duì)此進(jìn)行一一介紹:
1). 使用new關(guān)鍵字創(chuàng)建對(duì)象
?這是我們最常見(jiàn)的也是最簡(jiǎn)單的創(chuàng)建對(duì)象的方式,通過(guò)這種方式我們可以調(diào)用任意的構(gòu)造函數(shù)(無(wú)參的和有參的)去創(chuàng)建對(duì)象饰潜。比如:
Student student = new Student();
2). 使用Class類的newInstance方法(反射機(jī)制)
?我們也可以通過(guò)Java的反射機(jī)制使用Class類的newInstance方法來(lái)創(chuàng)建對(duì)象初坠,事實(shí)上,這個(gè)newInstance方法調(diào)用無(wú)參的構(gòu)造器創(chuàng)建對(duì)象囊拜,比如:
Student student2 = (Student)Class.forName("Student類全限定名").newInstance();
或者:
Student stu = Student.class.newInstance();
3). 使用Constructor類的newInstance方法(反射機(jī)制)
?java.lang.relect.Constructor類里也有一個(gè)newInstance方法可以創(chuàng)建對(duì)象某筐,該方法和Class類中的newInstance方法很像,但是相比之下冠跷,Constructor類的newInstance方法更加強(qiáng)大些南誊,我們可以通過(guò)這個(gè)newInstance方法調(diào)用有參數(shù)的和私有的構(gòu)造函數(shù),比如:
public class Student {
private int id;
public Student(Integer id) {
this.id = id;
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
}
}
使用newInstance方法的這兩種方式創(chuàng)建對(duì)象使用的就是Java的反射機(jī)制蜜托,事實(shí)上Class的newInstance方法內(nèi)部調(diào)用的也是Constructor的newInstance方法抄囚。
4). 使用Clone方法創(chuàng)建對(duì)象
?無(wú)論何時(shí)我們調(diào)用一個(gè)對(duì)象的clone方法,JVM都會(huì)幫我們創(chuàng)建一個(gè)新的橄务、一樣的對(duì)象幔托,特別需要說(shuō)明的是,用clone方法創(chuàng)建對(duì)象的過(guò)程中并不會(huì)調(diào)用任何構(gòu)造函數(shù)蜂挪。簡(jiǎn)單而言重挑,要想使用clone方法,我們就必須先實(shí)現(xiàn)Cloneable接口并實(shí)現(xiàn)其定義的clone方法棠涮,這也是原型模式的應(yīng)用谬哀。比如:
public class Student implements Cloneable{
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
Student stu4 = (Student) stu3.clone();
}
}
5). 使用(反)序列化機(jī)制創(chuàng)建對(duì)象
?當(dāng)我們反序列化一個(gè)對(duì)象時(shí),JVM會(huì)給我們創(chuàng)建一個(gè)單獨(dú)的對(duì)象严肪,在此過(guò)程中史煎,JVM并不會(huì)調(diào)用任何構(gòu)造函數(shù)。為了反序列化一個(gè)對(duì)象驳糯,我們需要讓我們的類實(shí)現(xiàn)Serializable接口篇梭,比如:
public class Student implements Cloneable, Serializable {
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = constructor.newInstance(123);
// 寫對(duì)象
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("student.bin"));
output.writeObject(stu3);
output.close();
// 讀對(duì)象
ObjectInputStream input = new ObjectInputStream(new FileInputStream(
"student.bin"));
Student stu5 = (Student) input.readObject();
System.out.println(stu5);
}
}
6). 完整實(shí)例
public class Student implements Cloneable, Serializable {
private int id;
public Student() {
}
public Student(Integer id) {
this.id = id;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
public static void main(String[] args) throws Exception {
System.out.println("使用new關(guān)鍵字創(chuàng)建對(duì)象:");
Student stu1 = new Student(123);
System.out.println(stu1);
System.out.println("\n---------------------------\n");
System.out.println("使用Class類的newInstance方法創(chuàng)建對(duì)象:");
Student stu2 = Student.class.newInstance(); //對(duì)應(yīng)類必須具有無(wú)參構(gòu)造方法,且只有這一種創(chuàng)建方式
System.out.println(stu2);
System.out.println("\n---------------------------\n");
System.out.println("使用Constructor類的newInstance方法創(chuàng)建對(duì)象:");
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class); // 調(diào)用有參構(gòu)造方法
Student stu3 = constructor.newInstance(123);
System.out.println(stu3);
System.out.println("\n---------------------------\n");
System.out.println("使用Clone方法創(chuàng)建對(duì)象:");
Student stu4 = (Student) stu3.clone();
System.out.println(stu4);
System.out.println("\n---------------------------\n");
System.out.println("使用(反)序列化機(jī)制創(chuàng)建對(duì)象:");
// 寫對(duì)象
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("student.bin"));
output.writeObject(stu4);
output.close();
// 讀取對(duì)象
ObjectInputStream input = new ObjectInputStream(new FileInputStream(
"student.bin"));
Student stu5 = (Student) input.readObject();
System.out.println(stu5);
}
}/* Output:
使用new關(guān)鍵字創(chuàng)建對(duì)象:
Student [id=123]
---------------------------
使用Class類的newInstance方法創(chuàng)建對(duì)象:
Student [id=0]
---------------------------
使用Constructor類的newInstance方法創(chuàng)建對(duì)象:
Student [id=123]
---------------------------
使用Clone方法創(chuàng)建對(duì)象:
Student [id=123]
---------------------------
使用(反)序列化機(jī)制創(chuàng)建對(duì)象:
Student [id=123]
*///:~
?從Java虛擬機(jī)層面看酝枢,除了使用new關(guān)鍵字創(chuàng)建對(duì)象的方式外恬偷,其他方式全部都是通過(guò)轉(zhuǎn)變?yōu)閕nvokevirtual指令直接創(chuàng)建對(duì)象的。
二. Java 對(duì)象的創(chuàng)建過(guò)程
?當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí)帘睦,虛擬機(jī)就會(huì)為其分配內(nèi)存來(lái)存放對(duì)象自己的實(shí)例變量及其從父類繼承過(guò)來(lái)的實(shí)例變量(即使這些從超類繼承過(guò)來(lái)的實(shí)例變量有可能被隱藏也會(huì)被分配空間)喉磁。在為這些實(shí)例變量分配內(nèi)存的同時(shí),這些實(shí)例變量也會(huì)被賦予默認(rèn)值(零值)官脓。在內(nèi)存分配完成之后协怒,Java虛擬機(jī)就會(huì)開(kāi)始對(duì)新創(chuàng)建的對(duì)象按照程序員的意志進(jìn)行初始化。在Java對(duì)象初始化過(guò)程中卑笨,主要涉及三種執(zhí)行對(duì)象初始化的結(jié)構(gòu)孕暇,分別是 實(shí)例變量初始化、實(shí)例代碼塊初始化 以及 構(gòu)造函數(shù)初始化赤兴。
1妖滔、實(shí)例變量初始化與實(shí)例代碼塊初始化
?我們?cè)诙x(聲明)實(shí)例變量的同時(shí),還可以直接對(duì)實(shí)例變量進(jìn)行賦值或者使用實(shí)例代碼塊對(duì)其進(jìn)行賦值桶良。如果我們以這兩種方式為實(shí)例變量進(jìn)行初始化座舍,那么它們將在構(gòu)造函數(shù)執(zhí)行之前完成這些初始化操作陨帆。實(shí)際上曲秉,如果我們對(duì)實(shí)例變量直接賦值或者使用實(shí)例代碼塊賦值采蚀,那么編譯器會(huì)將其中的代碼放到類的構(gòu)造函數(shù)中去,并且這些代碼會(huì)被放在對(duì)超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句之后
(還記得嗎妆够?Java要求構(gòu)造函數(shù)的第一條語(yǔ)句必須是超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句),構(gòu)造函數(shù)本身的代碼之前鸵荠。例如:
public class InstanceVariableInitializer {
private int i = 1;
private int j = i + 1;
public InstanceVariableInitializer(int var){
System.out.println(i);
System.out.println(j);
this.i = var;
System.out.println(i);
System.out.println(j);
}
{ // 實(shí)例代碼塊
j += 3;
}
public static void main(String[] args) {
new InstanceVariableInitializer(8);
}
}/* Output:
1
5
8
5
*///:~
?上面的例子正好印證了上面的結(jié)論揩徊。特別需要注意的是腰鬼,Java是按照編程順序來(lái)執(zhí)行實(shí)例變量初始化器和實(shí)例初始化器中的代碼的,并且不允許順序靠前的實(shí)例代碼塊初始化在其后面定義的實(shí)例變量塑荒,
比如:
public class InstanceInitializer {
{
j = i;
}
private int i = 1;
private int j;
}
public class InstanceInitializer {
private int j = i;
private int i = 1;
}
?上面的這些代碼都是無(wú)法通過(guò)編譯的熄赡,編譯器會(huì)抱怨說(shuō)我們使用了一個(gè)未經(jīng)定義的變量。之所以要這么做是為了保證一個(gè)變量在被使用之前已經(jīng)被正確地初始化齿税。但是我們?nèi)匀挥修k法繞過(guò)這種檢查彼硫,比如:
public class InstanceInitializer {
private int j = getI();
private int i = 1;
public InstanceInitializer() {
i = 2;
}
private int getI() {
return i;
}
public static void main(String[] args) {
InstanceInitializer ii = new InstanceInitializer();
System.out.println(ii.j);
}
}
?如果我們執(zhí)行上面這段代碼,那么會(huì)發(fā)現(xiàn)打印的結(jié)果是0凌箕。因此我們可以確信拧篮,變量j被賦予了i的默認(rèn)值0,這一動(dòng)作發(fā)生在實(shí)例變量i初始化之前和構(gòu)造函數(shù)調(diào)用之前牵舱。
2串绩、構(gòu)造函數(shù)初始化
?我們可以從上文知道,實(shí)例變量初始化與實(shí)例代碼塊初始化總是發(fā)生在構(gòu)造函數(shù)初始化之前芜壁,那么我們下面著重看看構(gòu)造函數(shù)初始化過(guò)程礁凡。
眾所周知,每一個(gè)Java中的對(duì)象都至少會(huì)有一個(gè)構(gòu)造函數(shù)慧妄,如果我們沒(méi)有顯式定義構(gòu)造函數(shù)顷牌,那么它將會(huì)有一個(gè)默認(rèn)無(wú)參的構(gòu)造函數(shù)。在編譯生成的字節(jié)碼中塞淹,這些構(gòu)造函數(shù)會(huì)被命名成<init>()方法窟蓝,參數(shù)列表與Java語(yǔ)言書寫的構(gòu)造函數(shù)的參數(shù)列表相同。
?我們知道饱普,Java要求在實(shí)例化類之前运挫,必須先實(shí)例化其超類状共,以保證所創(chuàng)建實(shí)例的完整性。事實(shí)上滑臊,這一點(diǎn)是在構(gòu)造函數(shù)中保證的:Java強(qiáng)制要求Object對(duì)象(Object是Java的頂層對(duì)象口芍,沒(méi)有超類)之外的所有對(duì)象構(gòu)造函數(shù)的第一條語(yǔ)句必須是超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句或者是類中定義的其他的構(gòu)造函數(shù)箍铲,如果我們既沒(méi)有調(diào)用其他的構(gòu)造函數(shù)雇卷,也沒(méi)有顯式調(diào)用超類的構(gòu)造函數(shù),那么編譯器會(huì)為我們自動(dòng)生成一個(gè)對(duì)超類構(gòu)造函數(shù)的調(diào)用颠猴,
比如:
public class ConstructorExample {
}
?對(duì)于上面代碼中定義的類关划,我們觀察編譯之后的字節(jié)碼,我們會(huì)發(fā)現(xiàn)編譯器為我們生成一個(gè)構(gòu)造函數(shù)翘瓮,如下贮折,
aload_0
invokespecial #8; //Method java/lang/Object."<init>":()V
return
?上面代碼的第二行就是調(diào)用Object類的默認(rèn)構(gòu)造函數(shù)的指令。也就是說(shuō)资盅,如果我們顯式調(diào)用超類的構(gòu)造函數(shù)调榄,那么該調(diào)用必須放在構(gòu)造函數(shù)所有代碼的最前面,也就是必須是構(gòu)造函數(shù)的第一條指令呵扛。正因?yàn)槿绱嗣壳欤琂ava才可以使得一個(gè)對(duì)象在初始化之前其所有的超類都被初始化完成,并保證創(chuàng)建一個(gè)完整的對(duì)象出來(lái)今穿。
?特別地缤灵,如果我們?cè)谝粋€(gè)構(gòu)造函數(shù)中調(diào)用另外一個(gè)構(gòu)造函數(shù),如下所示蓝晒,
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1);
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
?對(duì)于這種情況腮出,Java只允許在ConstructorExample(int i)內(nèi)調(diào)用超類的構(gòu)造函數(shù),也就是說(shuō)芝薇,下面兩種情形的代碼編譯是無(wú)法通過(guò)的:
public class ConstructorExample {
private int i;
ConstructorExample() {
super();
this(1); // Error:Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
或者胚嘲,
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1);
super(); //Error: Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
this.i = i;
}
}
3、 小結(jié)
?總而言之洛二,實(shí)例化一個(gè)類的對(duì)象的過(guò)程是一個(gè)典型的遞歸過(guò)程馋劈,如下圖所示。進(jìn)一步地說(shuō)灭红,在實(shí)例化一個(gè)類的對(duì)象時(shí)侣滩,具體過(guò)程是這樣的:
?在準(zhǔn)備實(shí)例化一個(gè)類的對(duì)象前,首先準(zhǔn)備實(shí)例化該類的父類变擒,如果該類的父類還有父類君珠,那么準(zhǔn)備實(shí)例化該類的父類的父類,依次遞歸直到遞歸到Object類娇斑。此時(shí)策添,首先實(shí)例化Object類材部,再依次對(duì)以下各類進(jìn)行實(shí)例化,直到完成對(duì)目標(biāo)類的實(shí)例化唯竹。具體而言乐导,在實(shí)例化每個(gè)類時(shí),都遵循如下順序:先依次執(zhí)行實(shí)例變量初始化和實(shí)例代碼塊初始化浸颓,再執(zhí)行構(gòu)造函數(shù)初始化物臂。也就是說(shuō),編譯器會(huì)將實(shí)例變量初始化和實(shí)例代碼塊初始化相關(guān)代碼放到類的構(gòu)造函數(shù)中去产上,并且這些代碼會(huì)被放在對(duì)超類構(gòu)造函數(shù)的調(diào)用語(yǔ)句之后棵磷,構(gòu)造函數(shù)本身的代碼之前
。
4晋涣、實(shí)例變量初始化仪媒、實(shí)例代碼塊初始化以及構(gòu)造函數(shù)初始化綜合實(shí)例
在《JVM類加載機(jī)制》一文中詳細(xì)闡述了類初始化時(shí)機(jī)和初始化過(guò)程,并在文章的最后留了一個(gè)懸念給各位谢鹊,這里來(lái)揭開(kāi)這個(gè)懸念算吩。
//父類
class Foo {
int i = 1;
Foo() {
System.out.println(i); -----------(1)
int x = getValue();
System.out.println(x); -----------(2)
}
{
i = 2;
}
protected int getValue() {
return i;
}
}
//子類
class Bar extends Foo {
int j = 1;
Bar() {
j = 2;
}
{
j = 3;
}
@Override
protected int getValue() {
return j;
}
}
public class ConstructorExample {
public static void main(String... args) {
Bar bar = new Bar();
System.out.println(bar.getValue()); -----------(3)
}
}/* Output:
2
0
2
*///:~
?根據(jù)上文所述的類實(shí)例化過(guò)程,我們可以將Foo類的構(gòu)造函數(shù)和Bar類的構(gòu)造函數(shù)等價(jià)地分別變?yōu)槿缦滦问剑?/p>
//Foo類構(gòu)造函數(shù)的等價(jià)變換:
Foo() {
i = 1;
i = 2;
System.out.println(i);
int x = getValue();
System.out.println(x);
}
//Bar類構(gòu)造函數(shù)的等價(jià)變換
Bar() {
Foo();
j = 1;
j = 3;
j = 2
}
?這樣程序就好看多了佃扼,我們一眼就可以觀察出程序的輸出結(jié)果偎巢。在通過(guò)使用Bar類的構(gòu)造方法new一個(gè)Bar類的實(shí)例時(shí),首先會(huì)調(diào)用Foo類構(gòu)造函數(shù)松嘶,因此(1)處輸出是2艘狭,這從Foo類構(gòu)造函數(shù)的等價(jià)變換中可以直接看出。(2)處輸出是0翠订,為什么呢巢音?因?yàn)樵趫?zhí)行Foo的構(gòu)造函數(shù)的過(guò)程中,由于Bar重載了Foo中的getValue方法尽超,所以根據(jù)Java的多態(tài)特性可以知道官撼,其調(diào)用的getValue方法是被Bar重載的那個(gè)getValue方法。但由于這時(shí)Bar的構(gòu)造函數(shù)還沒(méi)有被執(zhí)行似谁,因此此時(shí)j的值還是默認(rèn)值0傲绣,因此(2)處輸出是0。最后巩踏,在執(zhí)行(3)處的代碼時(shí)秃诵,由于bar對(duì)象已經(jīng)創(chuàng)建完成,所以此時(shí)再訪問(wèn)j的值時(shí)塞琼,就得到了其初始化后的值2菠净,這一點(diǎn)可以從Bar類構(gòu)造函數(shù)的等價(jià)變換中直接看出。
三. 類的初始化時(shí)機(jī)與過(guò)程
?關(guān)于類的初始化時(shí)機(jī),筆者在博文《 JVM類加載機(jī)制概述:加載時(shí)機(jī)與加載過(guò)程》已經(jīng)介紹的很清楚了毅往,此處不再贅述牵咙。簡(jiǎn)單地說(shuō),在類加載過(guò)程中攀唯,準(zhǔn)備階段是正式為類變量(static 成員變量)分配內(nèi)存并設(shè)置類變量初始值(零值)的階段洁桌,而初始化階段是真正開(kāi)始執(zhí)行類中定義的java程序代碼(字節(jié)碼)并按程序猿的意圖去初始化類變量的過(guò)程。更直接地說(shuō)侯嘀,初始化階段就是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程另凌。<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)代碼塊static{}中的語(yǔ)句合并產(chǎn)生的,其中編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定残拐。
?類構(gòu)造器<clinit>()與實(shí)例構(gòu)造器<init>()不同途茫,它不需要程序員進(jìn)行顯式調(diào)用碟嘴,虛擬機(jī)會(huì)保證在子類類構(gòu)造器<clinit>()執(zhí)行之前溪食,父類的類構(gòu)造<clinit>()執(zhí)行完畢。由于父類的構(gòu)造器<clinit>()先執(zhí)行娜扇,也就意味著父類中定義的靜態(tài)代碼塊/靜態(tài)變量的初始化要優(yōu)先于子類的靜態(tài)代碼塊/靜態(tài)變量的初始化執(zhí)行错沃。特別地,類構(gòu)造器<clinit>()對(duì)于類或者接口來(lái)說(shuō)并不是必需的雀瓢,如果一個(gè)類中沒(méi)有靜態(tài)代碼塊枢析,也沒(méi)有對(duì)類變量的賦值操作,那么編譯器可以不為這個(gè)類生產(chǎn)類構(gòu)造器<clinit>()刃麸。此外醒叁,在同一個(gè)類加載器下,一個(gè)類只會(huì)被初始化一次泊业,但是一個(gè)類可以任意地實(shí)例化對(duì)象把沼。也就是說(shuō),在一個(gè)類的生命周期中吁伺,類構(gòu)造器<clinit>()最多會(huì)被虛擬機(jī)調(diào)用一次饮睬,而實(shí)例構(gòu)造器<init>()則會(huì)被虛擬機(jī)調(diào)用多次,只要程序員還在創(chuàng)建對(duì)象篮奄。
?注意捆愁,這里所謂的實(shí)例構(gòu)造器<init>()是指收集類中的所有實(shí)例變量的賦值動(dòng)作、實(shí)例代碼塊和構(gòu)造函數(shù)合并產(chǎn)生的窟却,類似于上文對(duì)Foo類的構(gòu)造函數(shù)和Bar類的構(gòu)造函數(shù)做的等價(jià)變換昼丑。
四. 總結(jié)
1、一個(gè)實(shí)例變量在對(duì)象初始化的過(guò)程中會(huì)被賦值幾次夸赫?
?我們知道菩帝,JVM在為一個(gè)對(duì)象分配完內(nèi)存之后,會(huì)給每一個(gè)實(shí)例變量賦予默認(rèn)值,這個(gè)時(shí)候?qū)嵗兞勘坏谝淮钨x值胁附,這個(gè)賦值過(guò)程是沒(méi)有辦法避免的酒繁。如果我們?cè)诼暶鲗?shí)例變量x的同時(shí)對(duì)其進(jìn)行了賦值操作,那么這個(gè)時(shí)候控妻,這個(gè)實(shí)例變量就被第二次賦值了州袒。如果我們?cè)趯?shí)例代碼塊中,又對(duì)變量x做了初始化操作弓候,那么這個(gè)時(shí)候郎哭,這個(gè)實(shí)例變量就被第三次賦值了。如果我們?cè)跇?gòu)造函數(shù)中菇存,也對(duì)變量x做了初始化操作夸研,那么這個(gè)時(shí)候,變量x就被第四次賦值依鸥。也就是說(shuō)亥至,在Java的對(duì)象初始化過(guò)程中,一個(gè)實(shí)例變量最多可以被初始化4次贱迟。
2姐扮、類的初始化過(guò)程與類的實(shí)例化過(guò)程的異同?
?類的初始化是指類加載過(guò)程中的初始化階段對(duì)類變量按照程序猿的意圖進(jìn)行賦值的過(guò)程衣吠;而類的實(shí)例化是指在類完全加載到內(nèi)存中后創(chuàng)建對(duì)象的過(guò)程茶敏。
3、假如一個(gè)類還未加載到內(nèi)存中缚俏,那么在創(chuàng)建一個(gè)該類的實(shí)例時(shí)惊搏,具體過(guò)程是怎樣的?
?我們知道忧换,要想創(chuàng)建一個(gè)類的實(shí)例恬惯,必須先將該類加載到內(nèi)存并進(jìn)行初始化,也就是說(shuō)包雀,類初始化操作是在類實(shí)例化操作之前進(jìn)行的宿崭,但并不意味著:只有類初始化操作結(jié)束后才能進(jìn)行類實(shí)例化操作。例如才写,筆者在博文《 JVM類加載機(jī)制概述:加載時(shí)機(jī)與加載過(guò)程》中所提到的下面這個(gè)經(jīng)典案例:
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}
static StaticTest st = new StaticTest();
static { //靜態(tài)代碼塊
System.out.println("1");
}
{ // 實(shí)例代碼塊
System.out.println("2");
}
StaticTest() { // 實(shí)例構(gòu)造器
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}
public static void staticFunction() { // 靜態(tài)方法
System.out.println("4");
}
int a = 110; // 實(shí)例變量
static int b = 112; // 靜態(tài)變量
}/* Output:
2
3
a=110,b=0
1
4
*///:~
總的來(lái)說(shuō)葡兑,類實(shí)例化的一般過(guò)程是:父類的類構(gòu)造器<clinit>() -> 子類的類構(gòu)造器<clinit>() -> 父類的成員變量和實(shí)例代碼塊 -> 父類的構(gòu)造函數(shù) -> 子類的成員變量和實(shí)例代碼塊 -> 子類的構(gòu)造函數(shù)。