今天整理一下內部類,其中包含了內部類的特殊形式,對比普通類有什么區別和作用,內部類和外圍類之間的聯系,內部類的擴展和被擴展,內部類編譯后文件的標識符。文章中有任何錯誤或遺漏請告知博主~
前置概念
嵌套類型:將一個類innerClass
或者接口innerInterface
的定義放在另一個類outterClass
或者接口outterInterface
定義的內部,那么類innerClass
就是內部類、outterClass
則是外圍類,類似的接口innerInterface
為嵌套接口,接口outterInterface
為外圍接口。
從這里可以看出嵌套類型之間的區別取決于內部類型是一個類還是一個接口,以及它的外圍類型是一個類還是一個接口。內部類型可以是靜態的,也可以不是:前者允許簡單的類型結構,后者定義了它和包圍類對象之間的特別關系。
例如:
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();
}
}
在這篇文章中,從最常見的類中嵌套類開始理解整個嵌套類型~
內部類
知道了內部類的概念之后,可以根據類的各種形式得到一些特殊的內部類,其中包括,靜態內部類,局部內部類,匿名內部類。在對整個內部類應該有個大體的認識后,在這里拋出疑問,為什么需要內部類,僅僅是因為定義在另一個類的內部么?
其實問題在《Thinking in Java》中有早有說明:每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對于內部類來說都沒有影響。這句話說明了它可以提供一個功能,即,使用內部類后,外圍類可以得到類似于多繼承的能力。但不僅于此,使用內部類還可以獲得其他一些特性:
1.內部類可以有多個實例,每個實例都有自己的狀態信息,并且與外圍類的對象信息相互獨立。
2.在單個外圍類中,可以用多個內部類以不同的形式實現同一個接口或繼承同一個類。
3.內部類屬于外圍類的一個輕量級可選組件。
4.內部類提供了更好的封裝效果,只能依靠外圍類來訪問。
注:《Thinking in Java》中寫的幾條特性里面只選了前面兩條,并去掉其中第三條換了第四條,如果有不同理解可以一起討論。
大體上,內部類的作用已經被明確了,但是我們并不清楚定義內部類是如何和外圍類進行聯系的,下面可以看一下內部類和外圍類的聯系機制。
內部類與外圍類的聯系
盡管我們知道內部類的作用和帶來的特性,但是并不清楚它是如何做到這些的,例如它能提供類似于多繼承的機制,但是內部類和外圍類又有什么關系?如果無法將內部類和外圍類聯系在一起,那么內部類僅僅只是寫在類定義里面的一個普通類,無法帶來它特殊的作用。如下代碼:
/**
***內部類與外圍類的聯系
**/
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
可以看到,內部類本身并沒有定義name
字段,它的方法是得到name
字段的值,輸出的name
字段的值是外圍類的name
字段的值,那么外圍類和內部類之間必然是有關聯的,這種關聯就是內部類特性和作用的關鍵。在上面的例子中,內部類自然擁有了外圍類成員的訪問權限,這是如何做到的呢?其實在代碼中可以看到,內部類InnerClass
實例inner
的創建是通過其外部類實例outter.new
出來的,那么內部類對象是有機會獲取外圍類對象的引用的。其實事實上也確實如此,在訪問外圍類成員的字段時,就是通過捕獲的外圍類對象引用(非static的內部類)這個引用是限定-this的引用(這個引用強調了一個概念:內部類對象是和包圍類對象緊密綁定在一起的,這也是同一個外圍類對象實現的一部分),通過這個引用可以訪問它的包圍類的所有成員,包括私有的。包圍類對象也可以訪問它內部類對象的私有成員,但只能先聲明字段才能訪問。
知道了內部類和外部類的聯系后,就可以靈活使用內部類語法規則來完成我們的設計,下面來看內部類的使用。
內部類的使用
創建
因為內部類提供了更好的封裝性,我們只能通過它的外圍類來訪問它,那么怎么在外圍類外部創建內部類對象呢?在沒有使用內部類的時候,我們創建一個類型的實例時,通常選擇使用new
關鍵字,但是在內部類這里,會發現new
關鍵字只有在外圍類內部才起作用,而在外圍類之外是無法new
出來的,其實這也是和內部類與外部類的聯系有關,內部類既然要自然擁有訪問外圍類實例的權限,自然要與外圍類實例聯系在一起,所以需要如上例所示通過外圍類實例使用new
創建:OutterClass.InnerClass inner = outter.new InnerClass();
當然這種特別的new
的方式不是唯一解,我們也可以選擇另一種創建方式:
/**
*** .this創建
**/
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
在這個例子中,我們直接通過方法返回一個內部類,在這個內部類中使用.this來綁定它的外圍類對象,輸出的結果也是正確的。
總結一下,有兩種方式在外部創建一個類的內部類,一種是.this
,另一種是.new
繼承
在之前,我們的內部類一直是一個基類。很多時候,我們使用的內部類,需要繼承一個已經存在的類,這個類中存在了一些基本的實現。既然是繼承那么內部類的繼承也是有選擇性的,它可以繼承一個普通類,也可以繼承一個其他類的內部類。在繼承普通類時,就像一般的類,但是如果它要繼承另外一個內部類,如下:
/**
***內部類繼承內部類
**/
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
的繼承關系去掉,就會提示錯誤,這說明,如果一個內部類要繼承另外一個內部類,那么需要它的外圍類也繼承它要繼承的內部類的外圍類,即InnerClass
要繼承InnerClass2
,則OutterClass
要繼承OutterClass2
。
把內部類繼承內部類說完之后,再看一下,外部類繼承包圍類的一個內部類,例如:
/**
***外部類繼承內部類
**/
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
是一個內部類,需要關聯一個外圍類,所以正確的寫法是class Unrelate extends OutterClass.InnerClass
,盡管Unrelate
類不是一個內部類,也不是一個外圍類,但是還是需要給它傳入一個外圍類對象綁定。而Unrelate
對象的使用和其他普通類并沒有什么不同。
作用字段,繼承和隱藏
在這里整體的分析一下內部類的作用情況。
首先,內部類獲取了它外圍類的引用,所以外圍類的所有字段和方法都是可以使用的,術語叫作 在作用字段內。但是,內部類自己也存在的字段和方法,如果內部類的字段和方法和外圍類的字段方法名一樣,會不會造成沖突?代碼如下:
/**
***內部類作用字段
**/
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
這里可以看到,輸出的結果和之前在InnerClass
中沒有name
字段時不一樣,inner
使用了它自己的字段name
,而屬于外圍類的字段name
和方法getName()
被隱藏了,其實隱藏外圍類的情況共有兩種:
1.內部類有自己的字段和方法。
2.內部類的父類有字段和方法。
在這兩種情況下,任意的簡單名用法,都會直接引用內部類成員。因為會出現隱藏的問題,所以在內部類內使用外圍類的字段和方法的時候,建議使用.this
來限定,例如:OutterClass.this.name
。
把一般的內部類說完之后,我們看一下幾種特別的內部類:
靜態內部類
定義:把一個靜態類定義在另一個類定義中,就是靜態內部類(嵌套類),和普通的內部類相比,定義時使用了static
關鍵字。
還記得之前,普通的內部類對象會在內部捕獲并保存它外圍類對象的引用,但是,靜態內部類不是這樣的。靜態內部類本身就是外圍類的一個成員,而不是一個獨立的對象存在的。因為沒有保存它外圍類對象,所以靜態內部類的創建不依賴于外圍類的引用,也沒有自然獲取的外圍類各種字段方法的權限。 例如:
/**
***靜態內部類
**/
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
關鍵字,使得它變成一個靜態內部類,那么使用OutterClass.this.name
的時候會提示錯誤,說明無法訪問外圍類的非靜態字段
局部內部類
定義:定義在外圍類的內部代碼塊中,它不時外圍類的一部分,但是能訪問當前代碼塊內的變量和所有的外圍類的成員,作用的范圍類似于局部變量只在當前代碼塊內部,因為外部沒有任何引用它的路徑。
局部內部類可以訪問定義的該類作用字段中的所有變量,包括代碼塊中的局部變量,外圍類的所有成員和局部內部類的所有字段,但是代碼塊中的局部變量或方法參數只能聲明成final才能被訪問。這是多線程的問題訪問保證安全性的措施,而且這樣的好處是值是確定的。在局部內部類中,也會存在普通內部類存在的隱藏字段方法等問題,如果隱藏的是代碼塊的局部變量,那么久沒有辦法來訪問這個被隱藏的變量了。
匿名內部類
定義:擴展了某個類或實現了某個接口的的匿名類。
匿名內部類沒有顯示聲明的構造器,但是可以使用初始代碼塊來初始化。雖然匿名類使用起來很簡單,但是會降低代碼的可讀性。
接口中的嵌套
雖然可以使用,但是目前來說,接口作為一個行為協議,盡量不要在內部書寫除協議本身以外的東西。
接口中嵌套類或者接口,本質上和類中嵌套類一樣,只是把一個與接口聯系緊密的類型關聯到這個接口的內部。例如:
interface OutterInterface{
class InnerClass{}
interface InnerInterface{}
InnerClass getInnerClass();
InnerInterface getInnerInterface();
}
這樣,內部的接口或者內部的類就和外圍接口緊密的綁定在一起。注:任何在接口中定義的類都是public
和static
的
類中嵌套接口
其實這個是很少很少用,目前來說,我還沒有見過,但是說明一下。它們在類中起到的作用僅僅只是組織相關類型的機制。由于接口沒有實現,所以它不能是非靜態的,默認的static
關鍵字省略。
內部類標識符
一般來說,我們的Demo.java
文件編譯后文件名為Demo.class
文件,但是如果java文件內部包含了內部類,那么文件會將內部類分出一個class文件,內部類的文件名為外圍類名$內部類名
,例如Outer
實體中還有Inner
類,那么編譯出來后,會存在Outer.class
文件和Outer$Inner.class
文件。那么如果是個匿名內部類呢,它本身就沒有名字,這種情況下,編譯器會簡單的產生一個數字作為它的標識符。例如Outer$1.class
。
寫到這里已經把內部類給寫完了,雖然后面部分只用文字描述了一下,還有一些東西沒有寫在上面,具體的可以再去看看研究研究,比如匿名內部類和局部內部類的區別等。
本文參考《Thinking in Java》第10章內部類,《Java程序設計語言》第5章 嵌套類和接口