成員內(nèi)部類
成員內(nèi)部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態(tài)成員)。
不過要注意的是士鸥,當成員內(nèi)部類擁有和外部類同名的成員變量或者方法時灼伤,會發(fā)生隱藏現(xiàn)象搏色,即默認情況下訪問的是成員內(nèi)部類的成員仰猖。如果要訪問外部類的同名成員,需要以下面的形式進行訪問:外部類.this.成員變量神得、外部類.this.成員方法沽损。
雖然成員內(nèi)部類可以無條件地訪問外部類的成員,而外部類想訪問成員內(nèi)部類的成員卻不是這么隨心所欲了循头。在外部類中如果要訪問成員內(nèi)部類的成員绵估,必須先創(chuàng)建一個成員內(nèi)部類的對象,再通過指向這個對象的引用來訪問卡骂。
成員內(nèi)部類是依附外部類而存在的国裳,也就是說,如果要創(chuàng)建成員內(nèi)部類的對象全跨,前提是必須存在一個外部類的對象缝左。創(chuàng)建成員內(nèi)部類對象的一般方式如下:
//第一種方式:
Outter outter =?new?Outter();
Outter.Inner inner = outter.new?Inner();?//必須通過Outter對象來創(chuàng)建
//第二種方式:
Outter.Inner inner1 = outter.getInnerInstance();
public Inner getInnerInstance() { if(inner == null) {inner = new Inner(); }return inner; }
內(nèi)部類可以擁有private訪問權(quán)限、protected訪問權(quán)限、public訪問權(quán)限及包訪問權(quán)限渺杉。如果成員內(nèi)部類Inner用private修飾蛇数,則只能在外部類的內(nèi)部訪問,如果用public修飾是越,則任何地方都能訪問耳舅;如果用protected修飾,則只能在同一個包下或者繼承外部類的情況下訪問倚评;如果是默認訪問權(quán)限浦徊,則只能在同一個包下訪問。這一點和外部類有一點不一樣天梧,外部類只能被public和包訪問兩種權(quán)限修飾盔性。我個人是這么理解的,由于成員內(nèi)部類看起來像是外部類的一個成員呢岗,所以可以像類的成員一樣擁有多種權(quán)限修飾冕香。
局部內(nèi)部類
局部內(nèi)部類是定義在一個方法或者一個作用域里面的類,它和成員內(nèi)部類的區(qū)別在于局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)后豫。
局部內(nèi)部類就像是方法里面的一個局部變量一樣悉尾,是不能有public、protected硬贯、private以及static修飾符的。
匿名內(nèi)部類
匿名內(nèi)部類是唯一一種沒有構(gòu)造器的類陨收。正因為其沒有構(gòu)造器饭豹,所以匿名內(nèi)部類的使用范圍非常有限,大部分匿名內(nèi)部類用于接口回調(diào)务漩。匿名內(nèi)部類在編譯的時候由系統(tǒng)自動起名為Outter$1.class拄衰。一般來說,匿名內(nèi)部類用于繼承其他類或是實現(xiàn)接口饵骨,并不需要增加額外的方法翘悉,只是對繼承方法的實現(xiàn)或是重寫。
靜態(tài)內(nèi)部類
靜態(tài)內(nèi)部類也是定義在另一個類里面的類居触,只不過在類的前面多了一個關(guān)鍵字static妖混。靜態(tài)內(nèi)部類是不需要依賴于外部類的,這點和類的靜態(tài)成員屬性有點類似轮洋,并且它不能使用外部類的非static成員變量或者方法,這點很好理解,因為在沒有外部類的對象的情況下问芬,可以創(chuàng)建靜態(tài)內(nèi)部類的對象楼肪,如果允許訪問外部類的非static成員就會產(chǎn)生矛盾,因為外部類的非static成員必須依附于具體的對象。
?Outter.Inner inner =?new?Outter.Inner();
深入理解內(nèi)部類
1.為什么成員內(nèi)部類可以無條件訪問外部類的成員误褪?
反編譯Outter$Inner.class里有一句:finalcom.cxh.test2.Outterthis$0;
這行是一個指向外部類對象的指針。也就是說編譯器會默認為成員內(nèi)部類添加了一個指向外部類對象的引用兽间,那么這個引用是如何賦初值的呢历葛?下面接著看內(nèi)部類的構(gòu)造器:publiccom.cxh.test2.Outter$Inner(com.cxh.test2.Outter);
從這里可以看出,雖然我們在定義的內(nèi)部類的構(gòu)造器是無參構(gòu)造器渡八,編譯器還是會默認添加一個參數(shù)啃洋,該參數(shù)的類型為指向外部類對象的一個引用,所以成員內(nèi)部類中的Outter this&0 指針便指向了外部類對象屎鳍,因此可以在成員內(nèi)部類中隨意訪問外部類的成員宏娄。從這里也間接說明了成員內(nèi)部類是依賴于外部類的,如果沒有創(chuàng)建外部類的對象逮壁,則無法對Outter this&0引用進行初始化賦值孵坚,也就無法創(chuàng)建成員內(nèi)部類的對象了。
2.為什么局部內(nèi)部類和匿名內(nèi)部類只能訪問局部final變量窥淆?
示例一:
這段代碼會被編譯成兩個class文件:Test.class和Test1.class卖宠。默認情況下,編譯器會為匿名內(nèi)部類和局部內(nèi)部類起名為Outter1.class忧饭。默認情況下扛伍,編譯器會為匿名內(nèi)部類和局部內(nèi)部類起名為Outterx.class(x為正整數(shù))。
上段代碼中词裤,如果把變量a和b前面的任一個final去掉刺洒,這段代碼都編譯不過。我們先考慮這樣一個問題:當test方法執(zhí)行完畢之后吼砂,變量a的生命周期就結(jié)束了逆航,而此時Thread對象的生命周期很可能還沒有結(jié)束,那么在Thread的run方法中繼續(xù)訪問變量a就變成不可能了渔肩,但是又要實現(xiàn)這樣的效果因俐,怎么辦呢?Java采用了?復制的手段來解決這個問題周偎。將這段代碼的字節(jié)碼反編譯可以得到下面的內(nèi)容抹剩,在run方法中有一條指令:bipush 10。
這條指令表示將操作數(shù)10壓棧蓉坎,表示使用的是一個本地局部變量吧兔。這個過程是在編譯期間由編譯器默認進行,如果這個變量的值在編譯期間可以確定袍嬉,則編譯器默認會在匿名內(nèi)部類(局部內(nèi)部類)的常量池中添加一個內(nèi)容相等的字面量或直接將相應(yīng)的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中境蔼。這樣一來灶平,匿名內(nèi)部類使用的變量是另一個局部變量,只不過值和方法中局部變量的值相等箍土,因此和方法中的局部變量完全獨立開逢享。
示例二:
我們看到匿名內(nèi)部類Test$1的構(gòu)造器含有兩個參數(shù),一個是指向外部類對象的引用吴藻,一個是int型變量瞒爬,很顯然,這里是將變量test方法中的形參a以參數(shù)的形式傳進來對匿名內(nèi)部類中的拷貝(變量a的拷貝)進行賦值初始化沟堡。
也就說如果局部變量的值在編譯期間就可以確定侧但,則直接在匿名內(nèi)部里面創(chuàng)建一個拷貝。如果局部變量的值無法在編譯期間確定航罗,則通過構(gòu)造器傳參的方式來對拷貝進行初始化賦值禀横。
在run方法中訪問的變量a根本就不是test方法中的局部變量a。這樣一來就解決了前面所說的 生命周期不一致的問題粥血。但是新的問題又來了柏锄,既然在run方法中訪問的變量a和test方法中的變量a不是同一個變量,當在run方法中改變變量a的值的話复亏,會出現(xiàn)什么情況趾娃?會造成數(shù)據(jù)不一致性,這樣就達不到原本的意圖和要求缔御。為了解決這個問題抬闷,java編譯器就限定必須將變量a限制為final變量,不允許對變量a進行更改(對于引用類型的變量耕突,是不允許指向新的對象)笤成,這樣數(shù)據(jù)不一致性的問題就得以解決了。
3.靜態(tài)內(nèi)部類有特殊的地方嗎有勾?
靜態(tài)內(nèi)部類是不依賴于外部類的疹启,也就說可以在不創(chuàng)建外部類對象的情況下創(chuàng)建內(nèi)部類的對象古程。另外蔼卡,靜態(tài)內(nèi)部類是不持有指向外部類對象的引用的。
內(nèi)部類的使用場景和好處
1.每個內(nèi)部類都能獨立的繼承一個接口的實現(xiàn)挣磨,所以無論外部類是否已經(jīng)繼承了某個(接口的)實現(xiàn)雇逞,對于內(nèi)部類都沒有影響。內(nèi)部類使得多繼承的解決方案變得完整茁裙。
2.方便將存在一定邏輯關(guān)系的類組織在一起塘砸,又可以對外界隱藏。
3.方便編寫事件驅(qū)動程序晤锥。
4.方便編寫線程代碼掉蔬。