今天整理一下內(nèi)部類焦影,其中包含了內(nèi)部類的特殊形式诲祸,對比普通類有什么區(qū)別和作用,內(nèi)部類和外圍類之間的聯(lián)系掀序,內(nèi)部類的擴展和被擴展帆焕,內(nèi)部類編譯后文件的標識符。文章中有任何錯誤或遺漏請告知博主~
前置概念
嵌套類型:將一個類innerClass
或者接口innerInterface
的定義放在另一個類outterClass
或者接口outterInterface
定義的內(nèi)部不恭,那么類innerClass
就是內(nèi)部類叶雹、outterClass
則是外圍類,類似的接口innerInterface
為嵌套接口换吧,接口outterInterface
為外圍接口折晦。
從這里可以看出嵌套類型之間的區(qū)別取決于內(nèi)部類型是一個類還是一個接口,以及它的外圍類型是一個類還是一個接口沾瓦。內(nèi)部類型可以是靜態(tài)的满着,也可以不是:前者允許簡單的類型結(jié)構(gòu),后者定義了它和包圍類對象之間的特別關(guān)系贯莺。
例如:
class OutterClass{
class InnerClass{
private String name;
}
interface InnerInterface{
String getName();
}
static class InnerClass2{
String name;
}
}
interface OutterInterface{
class InnerClass{
private String name;
String getName(){
return name;
}
}
interface InnerInterface{
void getName();
}
}
在這篇文章中风喇,從最常見的類中嵌套類開始理解整個嵌套類型~
內(nèi)部類
知道了內(nèi)部類的概念之后,可以根據(jù)類的各種形式得到一些特殊的內(nèi)部類缕探,其中包括魂莫,靜態(tài)內(nèi)部類,局部內(nèi)部類爹耗,匿名內(nèi)部類豁鲤。在對整個內(nèi)部類應(yīng)該有個大體的認識后,在這里拋出疑問鲸沮,為什么需要內(nèi)部類琳骡,僅僅是因為定義在另一個類的內(nèi)部么?
其實問題在《Thinking in Java》中有早有說明:每個內(nèi)部類都能獨立地繼承自一個(接口的)實現(xiàn)讼溺,所以無論外圍類是否已經(jīng)繼承了某個(接口的)實現(xiàn)楣号,對于內(nèi)部類來說都沒有影響。這句話說明了它可以提供一個功能,即炫狱,使用內(nèi)部類后藻懒,外圍類可以得到類似于多繼承的能力。但不僅于此视译,使用內(nèi)部類還可以獲得其他一些特性:
1.內(nèi)部類可以有多個實例嬉荆,每個實例都有自己的狀態(tài)信息,并且與外圍類的對象信息相互獨立酷含。
2.在單個外圍類中鄙早,可以用多個內(nèi)部類以不同的形式實現(xiàn)同一個接口或繼承同一個類。
3.內(nèi)部類屬于外圍類的一個輕量級可選組件椅亚。
4.內(nèi)部類提供了更好的封裝效果限番,只能依靠外圍類來訪問。
注:《Thinking in Java》中寫的幾條特性里面只選了前面兩條呀舔,并去掉其中第三條換了第四條弥虐,如果有不同理解可以一起討論。
大體上媚赖,內(nèi)部類的作用已經(jīng)被明確了霜瘪,但是我們并不清楚定義內(nèi)部類是如何和外圍類進行聯(lián)系的,下面可以看一下內(nèi)部類和外圍類的聯(lián)系機制惧磺。
內(nèi)部類與外圍類的聯(lián)系
盡管我們知道內(nèi)部類的作用和帶來的特性颖对,但是并不清楚它是如何做到這些的,例如它能提供類似于多繼承的機制豺妓,但是內(nèi)部類和外圍類又有什么關(guān)系惜互?如果無法將內(nèi)部類和外圍類聯(lián)系在一起,那么內(nèi)部類僅僅只是寫在類定義里面的一個普通類琳拭,無法帶來它特殊的作用训堆。如下代碼:
/**
***內(nèi)部類與外圍類的聯(lián)系
**/
public class InnerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.new InnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
class InnerClass{
public String getName(){
return name;
}
}
}
輸出:
--inner.getName = OutterClassName
可以看到,內(nèi)部類本身并沒有定義name
字段白嘁,它的方法是得到name
字段的值坑鱼,輸出的name
字段的值是外圍類的name
字段的值,那么外圍類和內(nèi)部類之間必然是有關(guān)聯(lián)的絮缅,這種關(guān)聯(lián)就是內(nèi)部類特性和作用的關(guān)鍵鲁沥。在上面的例子中,內(nèi)部類自然擁有了外圍類成員的訪問權(quán)限耕魄,這是如何做到的呢画恰?其實在代碼中可以看到,內(nèi)部類InnerClass
實例inner
的創(chuàng)建是通過其外部類實例outter.new
出來的吸奴,那么內(nèi)部類對象是有機會獲取外圍類對象的引用的允扇。其實事實上也確實如此缠局,在訪問外圍類成員的字段時,就是通過捕獲的外圍類對象引用(非static的內(nèi)部類)這個引用是限定-this的引用(這個引用強調(diào)了一個概念:內(nèi)部類對象是和包圍類對象緊密綁定在一起的考润,這也是同一個外圍類對象實現(xiàn)的一部分)狭园,通過這個引用可以訪問它的包圍類的所有成員,包括私有的糊治。包圍類對象也可以訪問它內(nèi)部類對象的私有成員唱矛,但只能先聲明字段才能訪問。
知道了內(nèi)部類和外部類的聯(lián)系后井辜,就可以靈活使用內(nèi)部類語法規(guī)則來完成我們的設(shè)計绎谦,下面來看內(nèi)部類的使用。
內(nèi)部類的使用
創(chuàng)建
因為內(nèi)部類提供了更好的封裝性抑胎,我們只能通過它的外圍類來訪問它燥滑,那么怎么在外圍類外部創(chuàng)建內(nèi)部類對象呢渐北?在沒有使用內(nèi)部類的時候阿逃,我們創(chuàng)建一個類型的實例時,通常選擇使用new
關(guān)鍵字赃蛛,但是在內(nèi)部類這里恃锉,會發(fā)現(xiàn)new
關(guān)鍵字只有在外圍類內(nèi)部才起作用,而在外圍類之外是無法new
出來的呕臂,其實這也是和內(nèi)部類與外部類的聯(lián)系有關(guān)破托,內(nèi)部類既然要自然擁有訪問外圍類實例的權(quán)限,自然要與外圍類實例聯(lián)系在一起歧蒋,所以需要如上例所示通過外圍類實例使用new
創(chuàng)建:OutterClass.InnerClass inner = outter.new InnerClass();
當然這種特別的new
的方式不是唯一解土砂,我們也可以選擇另一種創(chuàng)建方式:
/**
*** .this創(chuàng)建
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.getInnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
輸出:
--inner.getName = OutterClassName
在這個例子中,我們直接通過方法返回一個內(nèi)部類谜洽,在這個內(nèi)部類中使用.this來綁定它的外圍類對象萝映,輸出的結(jié)果也是正確的。
總結(jié)一下阐虚,有兩種方式在外部創(chuàng)建一個類的內(nèi)部類序臂,一種是.this
,另一種是.new
繼承
在之前实束,我們的內(nèi)部類一直是一個基類奥秆。很多時候,我們使用的內(nèi)部類咸灿,需要繼承一個已經(jīng)存在的類构订,這個類中存在了一些基本的實現(xiàn)。既然是繼承那么內(nèi)部類的繼承也是有選擇性的避矢,它可以繼承一個普通類悼瘾,也可以繼承一個其他類的內(nèi)部類签赃。在繼承普通類時,就像一般的類分尸,但是如果它要繼承另外一個內(nèi)部類锦聊,如下:
/**
***內(nèi)部類繼承內(nèi)部類
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.getInnerClass();
System.out.println("--inner.getName = "+inner.getName());
}
}
class OutterClass extends OutterClass2{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass extends InnerClass2{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
class OutterClass2{
private String name = "OutterClassName2";
public InnerClass2 getInnerClass(){
return new InnerClass2();
}
class InnerClass2{
public OutterClass2 getOutterClass(){
return OutterClass2.this;
}
public String getName(){
return name;
}
}
}
輸出:
--inner.getName = OutterClassName
在這里可以看到InnerClass
繼承了InnerClass2
,程序也是正確的運行的箩绍,當我把InnerClass
類的外圍類OutterClass
的繼承關(guān)系去掉孔庭,就會提示錯誤,這說明材蛛,如果一個內(nèi)部類要繼承另外一個內(nèi)部類圆到,那么需要它的外圍類也繼承它要繼承的內(nèi)部類的外圍類,即InnerClass
要繼承InnerClass2
卑吭,則OutterClass
要繼承OutterClass2
芽淡。
把內(nèi)部類繼承內(nèi)部類說完之后,再看一下豆赏,外部類繼承包圍類的一個內(nèi)部類挣菲,例如:
/**
***外部類繼承內(nèi)部類
**/
public class innerClassDemo {
public static void main(String[] arg0){
Unrelate unrelate = new Unrelate(new OutterClass());
System.out.println("unrelateName = "+unrelate.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
class InnerClass{
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
return name;
}
}
}
class Unrelate extends OutterClass.InnerClass{
Unrelate(OutterClass outter){
outter.super();
}
}
輸出:
unrelateName = OutterClassName
在這里,如果直接class Unrelate extends InnerClass
會報錯掷邦,因為它的超類InnerClass
是一個內(nèi)部類白胀,需要關(guān)聯(lián)一個外圍類,所以正確的寫法是class Unrelate extends OutterClass.InnerClass
抚岗,盡管Unrelate
類不是一個內(nèi)部類或杠,也不是一個外圍類,但是還是需要給它傳入一個外圍類對象綁定宣蔚。而Unrelate
對象的使用和其他普通類并沒有什么不同向抢。
作用字段,繼承和隱藏
在這里整體的分析一下內(nèi)部類的作用情況胚委。
首先挟鸠,內(nèi)部類獲取了它外圍類的引用,所以外圍類的所有字段和方法都是可以使用的篷扩,術(shù)語叫作 在作用字段內(nèi)兄猩。但是,內(nèi)部類自己也存在的字段和方法鉴未,如果內(nèi)部類的字段和方法和外圍類的字段方法名一樣枢冤,會不會造成沖突?代碼如下:
/**
***內(nèi)部類作用字段
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass outter = new OutterClass();
OutterClass.InnerClass inner = outter.new InnerClass();
System.out.println("--getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public InnerClass getInnerClass(){
return new InnerClass();
}
public String getName(){
System.out.println("--OutterClass-getName-");
return name;
}
class InnerClass{
private String name = "InnerClassName";
public OutterClass getOutterClass(){
return OutterClass.this;
}
public String getName(){
System.out.println("--InnerClass-getName-");
return name;
}
}
}
輸出:
--InnerClass-getName-
--getName = InnerClassName
這里可以看到铜秆,輸出的結(jié)果和之前在InnerClass
中沒有name
字段時不一樣淹真,inner
使用了它自己的字段name
,而屬于外圍類的字段name
和方法getName()
被隱藏了连茧,其實隱藏外圍類的情況共有兩種:
1.內(nèi)部類有自己的字段和方法核蘸。
2.內(nèi)部類的父類有字段和方法巍糯。
在這兩種情況下,任意的簡單名用法客扎,都會直接引用內(nèi)部類成員祟峦。因為會出現(xiàn)隱藏的問題,所以在內(nèi)部類內(nèi)使用外圍類的字段和方法的時候徙鱼,建議使用.this
來限定宅楞,例如:OutterClass.this.name
。
把一般的內(nèi)部類說完之后袱吆,我們看一下幾種特別的內(nèi)部類:
靜態(tài)內(nèi)部類
定義:把一個靜態(tài)類定義在另一個類定義中厌衙,就是靜態(tài)內(nèi)部類(嵌套類),和普通的內(nèi)部類相比绞绒,定義時使用了static
關(guān)鍵字婶希。
還記得之前,普通的內(nèi)部類對象會在內(nèi)部捕獲并保存它外圍類對象的引用蓬衡,但是喻杈,靜態(tài)內(nèi)部類不是這樣的。靜態(tài)內(nèi)部類本身就是外圍類的一個成員撤蟆,而不是一個獨立的對象存在的奕塑。因為沒有保存它外圍類對象堂污,所以靜態(tài)內(nèi)部類的創(chuàng)建不依賴于外圍類的引用家肯,也沒有自然獲取的外圍類各種字段方法的權(quán)限。 例如:
/**
***靜態(tài)內(nèi)部類
**/
public class innerClassDemo {
public static void main(String[] arg0){
OutterClass.InnerClass2 inner = new OutterClass.InnerClass2();
System.out.println("--getName = "+inner.getName());
}
}
class OutterClass{
private String name = "OutterClassName";
public String getName(){
System.out.println("--OutterClass-getName-");
return name;
}
static class InnerClass2{
private String name = "InnerClassName2";
public String getName(){
System.out.println("--InnerClass2-getName-");
return name;
}
}
}
為InnerClass
類添加static
關(guān)鍵字盟猖,使得它變成一個靜態(tài)內(nèi)部類讨衣,那么使用OutterClass.this.name
的時候會提示錯誤,說明無法訪問外圍類的非靜態(tài)字段
局部內(nèi)部類
定義:定義在外圍類的內(nèi)部代碼塊中式镐,它不時外圍類的一部分反镇,但是能訪問當前代碼塊內(nèi)的變量和所有的外圍類的成員,作用的范圍類似于局部變量只在當前代碼塊內(nèi)部娘汞,因為外部沒有任何引用它的路徑歹茶。
局部內(nèi)部類可以訪問定義的該類作用字段中的所有變量,包括代碼塊中的局部變量你弦,外圍類的所有成員和局部內(nèi)部類的所有字段惊豺,但是代碼塊中的局部變量或方法參數(shù)只能聲明成final才能被訪問。這是多線程的問題訪問保證安全性的措施禽作,而且這樣的好處是值是確定的尸昧。在局部內(nèi)部類中,也會存在普通內(nèi)部類存在的隱藏字段方法等問題旷偿,如果隱藏的是代碼塊的局部變量烹俗,那么久沒有辦法來訪問這個被隱藏的變量了爆侣。
匿名內(nèi)部類
定義:擴展了某個類或?qū)崿F(xiàn)了某個接口的的匿名類。
匿名內(nèi)部類沒有顯示聲明的構(gòu)造器幢妄,但是可以使用初始代碼塊來初始化兔仰。雖然匿名類使用起來很簡單,但是會降低代碼的可讀性蕉鸳。
接口中的嵌套
雖然可以使用斋陪,但是目前來說,接口作為一個行為協(xié)議置吓,盡量不要在內(nèi)部書寫除協(xié)議本身以外的東西无虚。
接口中嵌套類或者接口,本質(zhì)上和類中嵌套類一樣衍锚,只是把一個與接口聯(lián)系緊密的類型關(guān)聯(lián)到這個接口的內(nèi)部友题。例如:
interface OutterInterface{
class InnerClass{}
interface InnerInterface{}
InnerClass getInnerClass();
InnerInterface getInnerInterface();
}
這樣,內(nèi)部的接口或者內(nèi)部的類就和外圍接口緊密的綁定在一起戴质。注:任何在接口中定義的類都是public
和static
的
類中嵌套接口
其實這個是很少很少用度宦,目前來說,我還沒有見過告匠,但是說明一下戈抄。它們在類中起到的作用僅僅只是組織相關(guān)類型的機制。由于接口沒有實現(xiàn)后专,所以它不能是非靜態(tài)的划鸽,默認的static
關(guān)鍵字省略。
內(nèi)部類標識符
一般來說戚哎,我們的Demo.java
文件編譯后文件名為Demo.class
文件裸诽,但是如果java文件內(nèi)部包含了內(nèi)部類,那么文件會將內(nèi)部類分出一個class文件型凳,內(nèi)部類的文件名為外圍類名$內(nèi)部類名
丈冬,例如Outer
實體中還有Inner
類,那么編譯出來后甘畅,會存在Outer.class
文件和Outer$Inner.class
文件埂蕊。那么如果是個匿名內(nèi)部類呢,它本身就沒有名字疏唾,這種情況下蓄氧,編譯器會簡單的產(chǎn)生一個數(shù)字作為它的標識符。例如Outer$1.class
荸实。
寫到這里已經(jīng)把內(nèi)部類給寫完了匀们,雖然后面部分只用文字描述了一下,還有一些東西沒有寫在上面准给,具體的可以再去看看研究研究泄朴,比如匿名內(nèi)部類和局部內(nèi)部類的區(qū)別等重抖。
本文參考《Thinking in Java》第10章內(nèi)部類,《Java程序設(shè)計語言》第5章 嵌套類和接口