從反編譯深入理解JAVA內(nèi)部類類結(jié)構(gòu)以及final關(guān)鍵字

1.為什么成員內(nèi)部類可以無條件訪問外部類的成員愉粤?

在此之前,我們已經(jīng)討論過了成員內(nèi)部類可以無條件訪問外部類的成員,那具體究竟是如何實(shí)現(xiàn)的呢铃辖?下面通過反編譯字節(jié)碼文件看看究竟。事實(shí)上猪叙,編譯器在進(jìn)行編譯的時候娇斩,會將成員內(nèi)部類單獨(dú)編譯成一個字節(jié)碼文件,下面是Outter.java的代碼:

public class Outter {
    private Inner inner = null;
    public Outter() {
    }

    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }

    protected class Inner {
        public Inner() {

        }
    }
}

編譯之后穴翩,出現(xiàn)了兩個字節(jié)碼文件:

image

反編譯Outter$Inner.class文件得到下面信息:

>E:\Workspace\Test\bin\com\cxh\test2>javap -v Outter$Inner
Compiled from "Outter.java"
public class com.cxh.test2.Outter$Inner extends java.lang.Object
  SourceFile: "Outter.java"
  InnerClass:
   #24= #1 of #22; //Inner=class com/cxh/test2/Outter$Inner of class com/cxh/tes
t2/Outter
  minor version: 0
  major version: 50
  Constant pool:
const #1 = class        #2;     //  com/cxh/test2/Outter$Inner
const #2 = Asciz        com/cxh/test2/Outter$Inner;
const #3 = class        #4;     //  java/lang/Object
const #4 = Asciz        java/lang/Object;
const #5 = Asciz        this$0;
const #6 = Asciz        Lcom/cxh/test2/Outter;;
const #7 = Asciz        <init>;
const #8 = Asciz        (Lcom/cxh/test2/Outter;)V;
const #9 = Asciz        Code;
const #10 = Field       #1.#11; //  com/cxh/test2/Outter$Inner.this$0:Lcom/cxh/t
est2/Outter;
const #11 = NameAndType #5:#6;//  this$0:Lcom/cxh/test2/Outter;
const #12 = Method      #3.#13; //  java/lang/Object."<init>":()V
const #13 = NameAndType #7:#14;//  "<init>":()V
const #14 = Asciz       ()V;
const #15 = Asciz       LineNumberTable;
const #16 = Asciz       LocalVariableTable;
const #17 = Asciz       this;
const #18 = Asciz       Lcom/cxh/test2/Outter$Inner;;
const #19 = Asciz       SourceFile;
const #20 = Asciz       Outter.java;
const #21 = Asciz       InnerClasses;
const #22 = class       #23;    //  com/cxh/test2/Outter
const #23 = Asciz       com/cxh/test2/Outter;
const #24 = Asciz       Inner;

{
final com.cxh.test2.Outter this$0;

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   putfield        #10; //Field this$0:Lcom/cxh/test2/Outter;
   5:   aload_0
   6:   invokespecial   #12; //Method java/lang/Object."<init>":()V
   9:   return
  LineNumberTable:
   line 16: 0
   line 18: 9

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      10      0    this       Lcom/cxh/test2/Outter$Inner;

}

第11行到35行是常量池的內(nèi)容犬第,下面逐一第38行的內(nèi)容:

final com.cxh.test2.Outter this$0;

這行是一個指向外部類對象的指針,看到這里想必大家豁然開朗了芒帕。也就是說編譯器會默認(rèn)為成員內(nèi)部類添加了一個指向外部類對象的引用歉嗓,那么這個引用是如何賦初值的呢?下面接著看內(nèi)部類的構(gòu)造器:

public com.cxh.test2.Outter$Inner(com.cxh.test2.Outter);

從這里可以看出背蟆,雖然我們在定義的內(nèi)部類的構(gòu)造器是無參構(gòu)造器鉴分,編譯器還是會默認(rèn)添加一個參數(shù),該參數(shù)的類型為指向外部類對象的一個引用带膀,所以成員內(nèi)部類中的Outter this&0 指針便指向了外部類對象志珍,因此可以在成員內(nèi)部類中隨意訪問外部類的成員。從這里也間接說明了成員內(nèi)部類是依賴于外部類的本砰,如果沒有創(chuàng)建外部類的對象碴裙,則無法對Outter this&0引用進(jìn)行初始化賦值,也就無法創(chuàng)建成員內(nèi)部類的對象了点额。所以舔株,如果在外部類沒有人引用的時候,而成員內(nèi)部類有人引用还棱,外部類因?yàn)楸粌?nèi)部類引用所以不會被回收载慈。這就是Android中常見的Activity內(nèi)存泄露產(chǎn)生的原因。

2.為什么局部內(nèi)部類和匿名內(nèi)部類只能訪問局部final變量珍手?

想必這個問題也曾經(jīng)困擾過很多人办铡,在討論這個問題之前,先看下面這段代碼:

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

這段代碼會被編譯成兩個class文件:Test.class和Test1.class琳要。默認(rèn)情況下寡具,編譯器會為匿名內(nèi)部類和局部內(nèi)部類起名為Outterx.class(x為正整數(shù))。

image

根據(jù)上圖可知稚补,test方法中的匿名內(nèi)部類的名字被起為 Test$1童叠。

上段代碼中,如果把變量a和b前面的任一個final去掉课幕,這段代碼都編譯不過厦坛。我們先考慮這樣一個問題:

當(dāng)test方法執(zhí)行完畢之后五垮,變量a的生命周期就結(jié)束了,而此時Thread對象的生命周期很可能還沒有結(jié)束杜秸,那么在Thread的run方法中繼續(xù)訪問變量a就變成不可能了放仗,但是又要實(shí)現(xiàn)這樣的效果,怎么辦呢撬碟?Java采用了 復(fù)制 的手段來解決這個問題诞挨。將這段代碼的字節(jié)碼反編譯可以得到下面的內(nèi)容:

image

我們看到在run方法中有一條指令:

bipush 10

這條指令表示將操作數(shù)10壓棧,表示使用的是一個本地局部變量小作。這個過程是在編譯期間由編譯器默認(rèn)進(jìn)行亭姥,如果這個變量的值在編譯期間可以確定,則編譯器默認(rèn)會在匿名內(nèi)部類(局部內(nèi)部類)的常量池中添加一個內(nèi)容相等的字面量或直接將相應(yīng)的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中顾稀。這樣一來,匿名內(nèi)部類使用的變量是另一個局部變量坝撑,只不過值和方法中局部變量的值相等静秆,因此和方法中的局部變量完全獨(dú)立開。

下面再看一個例子:

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int a) {
        new Thread(){
            public void run() {
                System.out.println(a);
            };
        }.start();
    }
}

反編譯得到:

image

我們看到匿名內(nèi)部類Test$1的構(gòu)造器含有兩個參數(shù)巡李,一個是指向外部類對象的引用抚笔,一個是int型變量,很顯然侨拦,這里是將變量test方法中的形參a以參數(shù)的形式傳進(jìn)來對匿名內(nèi)部類中的拷貝(變量a的拷貝)進(jìn)行賦值初始化殊橙。

也就說如果局部變量的值在編譯期間就可以確定,則直接在匿名內(nèi)部里面創(chuàng)建一個拷貝狱从。如果局部變量的值無法在編譯期間確定膨蛮,則通過構(gòu)造器傳參的方式來對拷貝進(jìn)行初始化賦值。

從上面可以看出季研,在run方法中訪問的變量a根本就不是test方法中的局部變量a敞葛。這樣一來就解決了前面所說的 生命周期不一致的問題。但是新的問題又來了与涡,既然在run方法中訪問的變量a和test方法中的變量a不是同一個變量惹谐,當(dāng)在run方法中改變變量a的值的話,會出現(xiàn)什么情況驼卖?

對氨肌,會造成數(shù)據(jù)不一致性,這樣就達(dá)不到原本的意圖和要求酌畜。為了解決這個問題怎囚,java編譯器就限定必須將變量a限制為final變量,不允許對變量a進(jìn)行更改(對于引用類型的變量檩奠,是不允許指向新的對象)桩了,這樣數(shù)據(jù)不一致性的問題就得以解決了附帽。

到這里,想必大家應(yīng)該清楚為何 方法中的局部變量和形參都必須用final進(jìn)行限定了井誉。

3.靜態(tài)內(nèi)部類有特殊的地方嗎蕉扮?

從前面可以知道,靜態(tài)內(nèi)部類是不依賴于外部類的颗圣,也就說可以在不創(chuàng)建外部類對象的情況下創(chuàng)建內(nèi)部類的對象喳钟。另外,靜態(tài)內(nèi)部類是不持有指向外部類對象的引用的在岂,這個讀者可以自己嘗試反編譯class文件看一下就知道了奔则,是沒有Outter this&0引用的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔽午,一起剝皮案震驚了整個濱河市易茬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌及老,老刑警劉巖抽莱,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異骄恶,居然都是意外死亡食铐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門僧鲁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虐呻,“玉大人,你說我怎么就攤上這事寞秃≌宓穑” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵蜕该,是天一觀的道長犁柜。 經(jīng)常有香客問我,道長堂淡,這世上最難降的妖魔是什么馋缅? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮绢淀,結(jié)果婚禮上萤悴,老公的妹妹穿的比我還像新娘。我一直安慰自己皆的,他們只是感情好覆履,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般栖雾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上伟众,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天析藕,我揣著相機(jī)與錄音,去河邊找鬼凳厢。 笑死账胧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的先紫。 我是一名探鬼主播治泥,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼遮精!你這毒婦竟也來了居夹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤仑鸥,失蹤者是張志新(化名)和其女友劉穎吮播,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眼俊,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年粟关,在試婚紗的時候發(fā)現(xiàn)自己被綠了疮胖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡闷板,死狀恐怖澎灸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遮晚,我是刑警寧澤性昭,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站县遣,受9級特大地震影響糜颠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萧求,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一其兴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夸政,春花似錦元旬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坑资。三九已至,卻和暖如春穆端,著一層夾襖步出監(jiān)牢的瞬間袱贮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工徙赢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留字柠,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓狡赐,卻偏偏與公主長得像窑业,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子枕屉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容

  • 說起內(nèi)部類這個詞常柄,想必很多人都不陌生,但是又會覺得不熟悉搀擂。原因是平時編寫代碼時可能用到的場景不多西潘,用得最多的是在有...
    Java架構(gòu)師Carl閱讀 441評論 0 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法哨颂,內(nèi)部類的語法喷市,繼承相關(guān)的語法,異常的語法威恼,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • Advanced Language Features 知識點(diǎn):一. static修飾符 static修飾符可以用來...
    風(fēng)景涼閱讀 438評論 0 0
  • Java 內(nèi)部類 分四種:成員內(nèi)部類品姓、局部內(nèi)部類、靜態(tài)內(nèi)部類和匿名內(nèi)部類箫措。 1腹备、成員內(nèi)部類: 即作為外部類的一個成...
    ikaroskun閱讀 1,223評論 0 13
  • 冬日的暖陽 親吻著冰冷的大地 融化了冷漠的荒漠 從此,雪絨花不再拒人于 千里之外 墻角的梅花 在一個無霜的清晨 孤...
    心懷夢想1閱讀 146評論 0 0