作者:禪樓望月(http://m.survivalescaperooms.com/yaoyinglong)
更新:其實(shí)這里有好多的變戲法,只要你理解了他們?cè)贘VM的中的實(shí)現(xiàn)機(jī)制,就豁然開朗了。有時(shí)間我會(huì)把這些變戲法的東西說(shuō)明的。
Java 向程序員許下了美好的承諾:無(wú)需關(guān)心內(nèi)存的回收,Java提供了優(yōu)秀的垃圾回收機(jī)制來(lái)回收已經(jīng)分配的內(nèi)存。
所以初學(xué)者往往會(huì)肆無(wú)忌憚的揮霍Java內(nèi)存,從而導(dǎo)致Java程序的運(yùn)行效率下降,主要壞處為:

這說(shuō)明Java中定義實(shí)例成員變量時(shí),必須采用合法的前向引用。同樣兩個(gè)類成員變量也必須采用合法的前向引用:

但是,如果一個(gè)是實(shí)例成員變量,一個(gè)是類成員變量,則實(shí)例成員變量總是可以引用類成員變量:

這是因?yàn)椋?strong>類成員變量初始化時(shí)機(jī)總是在實(shí)例成員變量初始化時(shí)機(jī)之前,確切的說(shuō)是在類加載時(shí)進(jìn)行的。
2、靜態(tài)成員可實(shí)例成員使用static修飾的靜態(tài)變量屬于類本身,而實(shí)例變量數(shù)據(jù)類的實(shí)例。在同一JVM中,每個(gè)類只對(duì)應(yīng)一個(gè)Class對(duì)象,因此同一JVM內(nèi)的一個(gè)類的類變量只需一塊內(nèi)存空間,但每個(gè)類可以創(chuàng)建多個(gè)Java對(duì)象,因此JVM必須為每個(gè)Java對(duì)象的實(shí)例變量分配一塊內(nèi)存空間。
Java允許通過類來(lái)訪問類成員變量,也允許類實(shí)例訪問類成員變量,(Java這樣設(shè)計(jì)是不合理的)

但是java設(shè)計(jì)者,卻在類實(shí)例訪問類成員變量時(shí),底層依然轉(zhuǎn)換為類來(lái)訪問類成員變量。怎么證明呢?
通過反編譯來(lái)看看:

JVM在底層使用someTh對(duì)象所對(duì)應(yīng)的引用類型來(lái)調(diào)用靜態(tài)成員,這就給程序員造成了一定的錯(cuò)覺,以為調(diào)用的是自己對(duì)象的東西,但是改變靜態(tài)成員的值,在其他的對(duì)象的中會(huì)體現(xiàn)出來(lái),這個(gè)很危險(xiǎn):

在一個(gè)類實(shí)例中修改了類成員變量的值,在另一個(gè)類實(shí)例中卻體現(xiàn)出來(lái)了。
3 實(shí)例變量的初始化時(shí)機(jī)從語(yǔ)法角度看,我們可以在如下3個(gè)地方對(duì)實(shí)例變量執(zhí)行初始化:①定義實(shí)例變量是指定初始值②非靜態(tài)初始化塊中對(duì)實(shí)例變量指定初始值③構(gòu)造器中對(duì)實(shí)例變量指定初始值。其中①和②比③更早執(zhí)行,①和②那個(gè)更早執(zhí)行,就看那個(gè)在代碼中出現(xiàn)的更早。如:

由此可見類實(shí)例變量只能放在構(gòu)造器中初始化,但是作為程序員編程時(shí),可以放在定義處,也可以放在非靜態(tài)塊中,但是結(jié)果都是一樣的,JVM會(huì)把它們抽取出來(lái)放在構(gòu)造函數(shù)中。
4 類變量初始化時(shí)機(jī)從語(yǔ)法角度看,可以在如下兩個(gè)地方對(duì)類變量初始化:①定義類變量時(shí)初始化;②靜態(tài)初始化塊中對(duì)類變量指定初始值。這兩種方式的執(zhí)行順序和它們?cè)谠创a中出現(xiàn)的順序相同:

由此可見,類變量只能在靜態(tài)塊中被初始化,但是作為程序員編程來(lái)說(shuō),可以放在定義處也可以放在靜態(tài)快中,結(jié)果都是一樣的,JVM會(huì)把它們收取出來(lái)都放進(jìn)靜態(tài)快中。
5 父類構(gòu)造器
看如下代碼:

這里便引發(fā)了一個(gè)疑問:在這里Sub類還沒有被創(chuàng)建(因?yàn)檎{(diào)用display的時(shí)候父類的構(gòu)造函數(shù)還沒有走完,怎么會(huì)走子類的構(gòu)造函數(shù)),怎么能調(diào)用它的方法呢?誒!難道類實(shí)例不是由構(gòu)造器創(chuàng)建的嗎? 很多書籍中都是這樣說(shuō)的:類實(shí)例是由構(gòu)造器創(chuàng)建。 其實(shí),這句話是完全錯(cuò)誤的。實(shí)際的情況是構(gòu)造器只是負(fù)責(zé)對(duì)Java對(duì)象實(shí)例變量執(zhí)行初始化(即賦初始值),在執(zhí)行構(gòu)造器代碼之前,該對(duì)象所占的內(nèi)存已經(jīng)被分配下來(lái)了。這些內(nèi)存里的值都是各個(gè)類型的默認(rèn)值。 所以上面代碼在執(zhí)行new Sub();的時(shí)候系統(tǒng)已經(jīng)為Sub對(duì)象分配了內(nèi)存空間(兩塊內(nèi)存空間,一塊用于存放Sub的i另一塊用于存放Base的i(這一塊內(nèi)存,子類和父類共用,改變?nèi)魏我粋€(gè)另一個(gè)會(huì)跟著動(dòng)),原因是子類不能完全覆蓋父類的成員變量)注意: 對(duì)象是由new關(guān)鍵字創(chuàng)建的,在執(zhí)行new……的時(shí)候,一個(gè)Java對(duì)象已經(jīng)建成了,只是它的變量還沒有初始化,構(gòu)造函數(shù)的功能就是對(duì)這些變量進(jìn)行初始化。沒有運(yùn)行完構(gòu)造函數(shù)Java對(duì)象的方法是可以被調(diào)用的,因?yàn)樗鸵话鉐ava對(duì)象沒有任何的區(qū)別。再來(lái)看一段代碼:

是否會(huì)感覺到this指代有點(diǎn)混亂呢? 但是從打印出來(lái)的結(jié)果來(lái)看,this確實(shí)指代的是Sub,但是我們也知道,當(dāng)this在構(gòu)造器中this指的是正在被初始化Java對(duì)象。怎么理解呢?從源代碼看,此時(shí)this位于Base構(gòu)造器中,但是這些代碼實(shí)際放在Sub()構(gòu)造器內(nèi)執(zhí)行,是Sub()構(gòu)造器隱式調(diào)用了Base()構(gòu)造器的代碼。由此可見,this指的是Sub而不是Base。現(xiàn)在問題又來(lái)了,既然this指的是Sub,那么,為什么System.out.6 父子實(shí)例的內(nèi)存控制 由上圖可知: 1、變量d2b和d實(shí)際指向同一個(gè)對(duì)象,但是訪問他們的實(shí)例變量時(shí)卻輸出不同的值,這表明d2b和d變量所指向的java對(duì)象中包含了兩塊內(nèi)存,更別存放著值為2的count實(shí)例變量和值為20的count實(shí)例變量。2、不管d、db、d2b,只要它們指向一個(gè)Sub對(duì)象,不管聲明它們使用什么類型,當(dāng)通過這些變量來(lái)調(diào)用時(shí),方法的行為總是表現(xiàn)出它們實(shí)際類型的行為。但如果通過這些變量來(lái)訪問它們所指對(duì)象的實(shí)例變量,這些實(shí)例變量的值總是表現(xiàn)出聲明這些變量所用的類型的行為。由此可見Java繼承在處理成員變量和方法時(shí),是有區(qū)別的。 但是,還是可以通過super來(lái)調(diào)用父類中被覆蓋的方法。我們?cè)賮?lái)看一下這段代碼: 用javap工具查看: 由此可見子類繼承了父類的實(shí)例變量,內(nèi)存中值為父類中的變量申請(qǐng)了空間,并沒有為子類中該變量開辟內(nèi)存空間。有人可能說(shuō)你這里的實(shí)例變量x是private,其實(shí)即是public也是一樣的,不信的話可以試試。 那么,我們?cè)谧宇愔姓{(diào)用setX方法其實(shí),設(shè)置的是父類中的實(shí)例變量x。因?yàn)檫@個(gè)方法是從父類繼承過來(lái)的。由此也可以得出父類中一般不要設(shè)置靜態(tài)全局變量,這樣會(huì)有線程安全的問題。 所以在子類中使用super的意思是,使用自己對(duì)象里面保存的從父類繼承下來(lái)的那個(gè)方法。 由此可見,super本身并沒有引用任何對(duì)象,它只能算作一個(gè)標(biāo)記。它的作用僅限于在子類中(不是子類的對(duì)象)調(diào)用在父類中定義的,被隱藏了的實(shí)例變量,或者在子類中定義的,被覆蓋的方法。注意:雖然說(shuō)這是父類中的方法和變量。其實(shí)和父類沒有一點(diǎn)關(guān)系了。只是在調(diào)用上有點(diǎn)區(qū)別,其他的和類自己的方法沒什么區(qū)別。 記住:Java允許通過實(shí)例對(duì)象來(lái)調(diào)用類的靜態(tài)變量其他的和實(shí)例變量一樣。 final修飾的變量final修飾的變量必須顯示的指定初始值(普通變量系統(tǒng)會(huì)為其設(shè)置默認(rèn)值),而且只能在以下3個(gè)地方制定初始值: 對(duì)于一個(gè)final變量而言,不管它是類變量、實(shí)例變量還是局部變量,只要該變量被final修飾,并且被賦予的初始值(必須的),那么該在類編譯的時(shí)候就被確定了,那么,這個(gè)final變量就不再是變量了,而是相當(dāng)于一個(gè)直接量。 內(nèi)部類中的局部變量如果程序需要在內(nèi)部類中使用局部變量,那么這個(gè)局部變量必須由final修飾。但是為什么內(nèi)部類中要訪問的局部變量都必須使用final修飾呢?原因是:對(duì)于普通的局部變量,它的作用于就停留在該方法內(nèi),該方法結(jié)束后該局部變量就消失了;但是內(nèi)部類則可能產(chǎn)生隱式的“閉包”,閉包將使得局部變量脫離了它所在的方法繼續(xù)存在。

//父類public class Base { private int x=10; public int getX() { return x; } public void setX(int x) { this.x = x; } }//子類public class Sub extends Base { public Sub() { this.setX(20); } }//測(cè)試 public static void main(String[] args) { Base b=new Base(); System.out.println("我是父類:"+b.getX()); //-->10 Base base=new Sub(); System.out.println("我是父類:"+base.getX()); //-->20 Sub s=new Sub(); System.out.println("我是子類:"+s.getX()); //-->20 System.out.println("我是父類:"+base.getX()); //-->20 }





新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注