java對(duì)象初始化代碼詳解
本文主要記錄JAVA中對(duì)象的初始化過(guò)程,包括實(shí)例變量的初始化和類(lèi)變量的初始化以及final關(guān)鍵字對(duì)初始化的影響。另外,還討論了由于繼承原因,探討了引用變量的編譯時(shí)類(lèi)型和運(yùn)行時(shí)類(lèi)型
一,實(shí)例變量的初始化
這里首先介紹下創(chuàng)建對(duì)象的過(guò)程:
類(lèi)型為Dog的一個(gè)對(duì)象首次創(chuàng)建時(shí),或者Dog類(lèi)的static字段或static方法首次訪(fǎng)問(wèn)時(shí),Java解釋器必須找到Dog.class(在事先設(shè)定好的路徑里面搜索);
找到Dog.class后(它會(huì)創(chuàng)建一個(gè)Class對(duì)象),它的所有static初始化模塊都會(huì)運(yùn)行。因此,static初始化僅發(fā)生一次——在Class對(duì)象首次載入的時(shí)候;
創(chuàng)建一個(gè)newDog()時(shí),Dog對(duì)象的構(gòu)建進(jìn)程首先會(huì)在內(nèi)存堆(Heap)里為一個(gè)Dog對(duì)象分配足夠多的存儲(chǔ)空間;
這種存儲(chǔ)空間會(huì)清為零,將Dog中的所有基本類(lèi)型(Primitive)設(shè)為它們的默認(rèn)值(0用于數(shù)字,以及boolean和char的等價(jià)設(shè)定);
進(jìn)行成員字段定義時(shí)發(fā)生的所有初始化都會(huì)執(zhí)行;
執(zhí)行構(gòu)造函數(shù)。
然后,開(kāi)始對(duì)實(shí)例變量進(jìn)行初始化。一共有三種方式對(duì)實(shí)例變量進(jìn)行初始化:
①定義實(shí)例變量時(shí)指定初始值
②非靜態(tài)初始化塊中對(duì)實(shí)例變量進(jìn)行初始化
③構(gòu)造器中對(duì)實(shí)例變量進(jìn)行初始化
當(dāng)new對(duì)象初始化時(shí),①②要先于③執(zhí)行。而①②的順序則按照它們?cè)谠创a中定義的順序來(lái)執(zhí)行。
當(dāng)實(shí)例變量使用了final關(guān)鍵字修飾時(shí),如果是在定義該final實(shí)例變量時(shí)直接指定初始值進(jìn)行的初始化(第①種方式),則:該變量的初始值在編譯時(shí)就被確定下來(lái),那么該final變量就類(lèi)似于“宏變量”,相當(dāng)于JAVA中的直接常量。
public class Test { public static void main(String[] args) { final String str1 = "HelloWorld"; final String str2 = "Hello" + "World"; System.out.println(str1 == str2);//true final String str3 = "Hello" + String.valueOf("World"); System.out.println(str1 == str3);//false } }
第8行輸出false,是因?yàn)椋旱?行中str3需要通過(guò)valueOf方法調(diào)用之后才能確定。而不是在編譯時(shí)確定。
再來(lái)看一個(gè)示例:
public class Test { final String str1 = "HelloWorld"; final String str2 = "Hello" + "World"; final String str3; final String str4; { str3 = "HelloWorld"; } { System.out.println(str1 == str2);//true System.out.println(str1 == str3);//true // System.out.println(str1 == str4);//compile error } public Test() { str4 = "HelloWorld"; System.out.println(str1 == str4);//true } public static void main(String[] args) { new Test(); } }
把第13行的注釋去掉,會(huì)報(bào)編譯錯(cuò)誤“Theblankfinalfieldstr4maynothavebeeninitialized”
因?yàn)樽兞縮tr4是在構(gòu)造器中進(jìn)行初始化的。而前面提到:①定義實(shí)例變量時(shí)直接指定初始值(str1和str2的初始化)、②非靜態(tài)初始化塊中對(duì)實(shí)例變量進(jìn)行初始化(str3的初始化)要先于③構(gòu)造器中對(duì)實(shí)例變量進(jìn)行初始化。
另外,對(duì)于final修飾的實(shí)例變量必須顯示地對(duì)它進(jìn)行初始化,而不是通過(guò)構(gòu)造器(<clinit>)對(duì)之進(jìn)行默認(rèn)初始化。
public class Test { final String str1;//compile error---沒(méi)有顯示的使用①②③中的方式進(jìn)行初始化 String str2; }
str2可以通過(guò)構(gòu)造器對(duì)之進(jìn)行默認(rèn)的初始化,初始化為null。而對(duì)于final修飾的變量 str1,必須顯示地使用 上面提到的三種方式進(jìn)行初始化。如下面的這個(gè)Test.java(一共有22行的這個(gè)Test類(lèi))
public class Test { final String str1 = "Hello";//定義實(shí)例變量時(shí)指定初始值 final String str2;//非靜態(tài)初始化塊中對(duì)實(shí)例變量進(jìn)行初始化 final String str3;//構(gòu)造器中對(duì)實(shí)例變量進(jìn)行初始化 { str2 = "Hello"; } public Test() { str3 = "Hello"; } public void show(){ System.out.println(str1 + str1 == "HelloHello");//true System.out.println(str2 + str2 == "HelloHello");//false System.out.println(str3 + str3 == "HelloHello");//false } public static void main(String[] args) { new Test().show(); } }
由于str1采用的是第①種方式進(jìn)行的初始化,故在執(zhí)行15行:str1+str1連接操作時(shí),str1其實(shí)相當(dāng)于“宏變量”
而str2和str3并不是“宏變量”,故16-17行輸出false
在非靜態(tài)初始化代碼塊中初始化變量和在構(gòu)造器中初始化變量的一點(diǎn)小區(qū)別:因?yàn)闃?gòu)造器是可以重寫(xiě)的,比如你把某個(gè)實(shí)例變量放在無(wú)參的構(gòu)造器中進(jìn)行初始化,但是在new對(duì)象時(shí)卻調(diào)用的是有參數(shù)的構(gòu)造器,那就得注意該實(shí)例變量有沒(méi)有正確得到初始化了。
而放在非靜態(tài)初始化代碼塊中初始化變量時(shí),不管是調(diào)用有參的構(gòu)造器還是無(wú)參的構(gòu)造器,非靜態(tài)初始化代碼塊都會(huì)執(zhí)行。
二,類(lèi)變量的初始化
類(lèi)變量一共有兩個(gè)地方對(duì)之進(jìn)行初始化:
❶定義類(lèi)變量時(shí)指定初始值
❷靜態(tài)初始化代碼塊中進(jìn)行初始化
不管new多少個(gè)對(duì)象,類(lèi)變量的初始化只執(zhí)行一次。
三,繼承對(duì)初始化的影響
主要是理解編譯時(shí)類(lèi)型和運(yùn)行時(shí)類(lèi)型的不同,從這個(gè)不同中可以看出this關(guān)鍵字和super關(guān)鍵字的一些本質(zhì)區(qū)別。
class Fruit{ String color = "unknow"; public Fruit getThis(){ return this; } public void info(){ System.out.println("fruit's method"); } } public class Apple extends Fruit{ String color = "red";//與父類(lèi)同名的實(shí)例變量 @Override public void info() { System.out.println("apple's method"); } public void accessFruitInfo(){ super.info(); } public Fruit getSuper(){ return super.getThis(); } //for test purpose public static void main(String[] args) { Apple a = new Apple(); Fruit f = a.getSuper(); //Fruit f2 = a.getThis(); //System.out.println(f == f2);//true System.out.println(a == f);//true System.out.println(a.color);//red System.out.println(f.color);//unknow a.info();//"apple's method" f.info();//"apple's method" a.accessFruitInfo();//"fruit's method" } }
值得注意的地方有以下幾個(gè):
⒈第35行引用變量a和f都指向內(nèi)存中的同一個(gè)對(duì)象,36-37行調(diào)用它們的屬性時(shí),a.color是red,而f.color是unknow
因?yàn)椋琭變量的聲明類(lèi)型(編譯時(shí)類(lèi)型)為Fruit,當(dāng)訪(fǎng)問(wèn)屬性時(shí)是由聲明該變量的類(lèi)型來(lái)決定的。
⒉第39-40行,a.info()和f.info()都輸出“apple'smethod”
因?yàn)?,f變量的運(yùn)行時(shí)類(lèi)型為Apple,info()是Apple重載的父類(lèi)的一個(gè)方法。調(diào)用方法時(shí)由變量的運(yùn)行時(shí)類(lèi)型來(lái)決定。
⒊關(guān)于this關(guān)鍵字
當(dāng)在29行new一個(gè)Apple對(duì)象,在30行調(diào)用getSuper()方法時(shí),最終是執(zhí)行到第4行的returnthis
this的解釋是:返回調(diào)用本方法的對(duì)象。它返回的類(lèi)型是Fruit類(lèi)型(見(jiàn)getThis方法的返回值類(lèi)型),但實(shí)際上是Apple對(duì)象導(dǎo)致的getThis方法的調(diào)用。故,這里的this的聲明類(lèi)型是Fruit,而運(yùn)行時(shí)類(lèi)型是Apple
⒋關(guān)于super關(guān)鍵字
super與this是有區(qū)別的。this可以用來(lái)代表“當(dāng)前對(duì)象”,可用return返回。而對(duì)于super而言,沒(méi)有returnsuper;這樣的語(yǔ)句。
super主要是為了:在子類(lèi)中訪(fǎng)問(wèn)父類(lèi)中的屬性或者在子類(lèi)中調(diào)用父類(lèi)中的方法而引入的一個(gè)關(guān)鍵字。比如第24行。
⒌在父類(lèi)的構(gòu)造器中不要去調(diào)用被子類(lèi)覆蓋的方法(Override),或者說(shuō)在構(gòu)造父類(lèi)對(duì)象時(shí),不要依賴(lài)于子類(lèi)覆蓋了父類(lèi)的那些方法。這樣很可能會(huì)導(dǎo)致初始化的失敗(沒(méi)有正確地初始化對(duì)象)
因?yàn)椋呵懊娴?點(diǎn)和第2點(diǎn)談到了,對(duì)象(變量)有聲明時(shí)類(lèi)型(編譯時(shí)類(lèi)型)和運(yùn)行時(shí)類(lèi)型。而方法的調(diào)用取決于運(yùn)行時(shí)類(lèi)型。
當(dāng)new子類(lèi)對(duì)象時(shí),會(huì)首先去初始化父類(lèi)的屬性,而此時(shí)對(duì)象的運(yùn)行時(shí)類(lèi)型是子類(lèi),因此父類(lèi)的屬性的賦值若依賴(lài)于子類(lèi)中重載的方法,會(huì)導(dǎo)致父類(lèi)屬性得不到正確的初始化值。示例如下:
class Fruit{ String color; public Fruit() { color = this.getColor();//父類(lèi)color屬性初始化依賴(lài)于重載的方法getColor // color = getColor(); } public String getColor(){ return "unkonw"; } @Override public String toString() { return color; } } public class Apple extends Fruit{ @Override public String getColor() { return "color: " + color; } // public Apple() { // color = "red"; // } public static void main(String[] args) { System.out.println(new Apple());//color: null } }
Fruit類(lèi)的color屬性 沒(méi)有正確地被初始化為"unknow",而是為 null
主要是因?yàn)榈?行 this.getColor()調(diào)用的是Apple類(lèi)的getColor方法,而此時(shí)Apple類(lèi)的color屬性是直接從Fruit類(lèi)繼承的。
四,參考資料
Effective Java中文版 第2版 中文 PDF版 第二版第17條
相關(guān)文章
java環(huán)境變量path和classpath的配置
這篇文章主要為大家詳細(xì)介紹了java系統(tǒng)環(huán)境變量path和classpath的配置過(guò)程,感興趣的小伙伴們可以參考一下2016-07-07關(guān)于SpringMVC中控制器如何處理文件上傳的問(wèn)題
這篇文章主要介紹了關(guān)于SpringMVC中控制器如何處理文件上傳的問(wèn)題,在 Web 應(yīng)用程序中,文件上傳是一個(gè)常見(jiàn)的需求,例如用戶(hù)上傳頭像、上傳文檔等,本文將介紹 Spring MVC 中的控制器如何處理文件上傳,并提供示例代碼,需要的朋友可以參考下2023-07-07對(duì)java for 循環(huán)執(zhí)行順序的詳解
今天小編就為大家分享一篇對(duì)java for 循環(huán)執(zhí)行順序的詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-06-06使用Mybatis的PageHelper分頁(yè)工具的教程詳解
這篇文章主要介紹了使用Mybatis的PageHelper分頁(yè)工具的教程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09通過(guò)idea創(chuàng)建Spring Boot項(xiàng)目并配置啟動(dòng)過(guò)程圖解
這篇文章主要介紹了通過(guò)idea創(chuàng)建Spring Boot項(xiàng)目并配置啟動(dòng)過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11詳解Java關(guān)于JDK中時(shí)間日期的API
這篇文章主要介紹了詳解Java關(guān)于JDK中時(shí)間日期的API,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09mybatis中方法返回泛型與resultType不一致的解決
這篇文章主要介紹了mybatis中方法返回泛型與resultType不一致的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07