可以將一個類的定義放在另一個類的定義內部,這就是內部類。
為什么要使用內部類
為什么要使用內部類?在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
在我們程序設計中有時候會存在一些使用接口很難解決的問題,這個時候我們可以利用內部類提供的、可以繼承多個具體的或者抽象的類的能力來解決這些程序設計問題。可以這樣說,接口只是解決了部分問題,而內部類使得多重繼承的解決方案變得更加完整。
public interface Father { } public interface Mother { } public class Son implements Father, Mother { } public class Daughter implements Father{ class Mother_ implements Mother{ } }
其實對於這個實例我們確實是看不出來使用內部類存在何種優點,但是如果Father、Mother不是接口,而是抽象類或者具體類呢?這個時候我們就只能使用內部類才能實現多重繼承了。
其實使用內部類最大的優點就在於它能夠非常好的解決多重繼承的問題,但是如果我們不需要解決多重繼承問題,那么我們自然可以使用其他的編碼方式,但是使用內部類還能夠為我們帶來如下特性(摘自《Think in java》):
1、內部類可以用多個實例,每個實例都有自己的狀態信息,並且與其他外圍對象的信息相互獨立。
2、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或者繼承同一個類。
3、創建內部類對象的時刻並不依賴於外圍類對象的創建。
4、內部類並沒有令人迷惑的“is-a”關系,他就是一個獨立的實體。
5、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
內部類基礎
在這個部分主要介紹內部類如何使用外部類的屬性和方法,以及使用.this與.new。
當我們在創建一個內部類的時候,它無形中就與外圍類有了一種聯系,依賴於這種聯系,它可以無限制地訪問外圍類的元素。
public class OuterClass { private String name ; private int age; /**省略getter和setter方法**/ public class InnerClass{ public InnerClass(){ name = "chenhao"; age = 23; } public void display(){ System.out.println("name:" + getName() +" ;age:" + getAge()); } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.display(); } } -------------- Output: name:chenhao ;age:23
在這個應用程序中,我們可以看到內部了InnerClass可以對外圍類OuterClass的屬性進行無縫的訪問,盡管它是private修飾的。這是因為當我們在創建某個外圍類的內部類對象時,此時內部類對象必定會捕獲一個指向那個外圍類對象的引用,只要我們在訪問外圍類的成員時,就會用這個引用來選擇外圍類的成員。
其實在這個應用程序中我們還看到了如何來引用內部類:引用內部類我們需要指明這個對象的類型:OuterClasName.InnerClassName。同時如果我們需要創建某個內部類對象,必須要利用外部類的對象通過.new來創建內部類:OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
同時如果我們需要生成對外部類對象的引用,可以使用OuterClassName.this,這樣就能夠產生一個正確引用外部類的引用了。當然這點實在編譯期就知曉了,沒有任何運行時的成本。
public class OuterClass { public void display(){ System.out.println("OuterClass..."); } public class InnerClass{ public OuterClass getOuterClass(){ return OuterClass.this; } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.getOuterClass().display(); } } ------------- Output: OuterClass...
到這里了我們需要明確一點,內部類是個編譯時的概念,一旦編譯成功后,它就與外圍類屬於兩個完全不同的類(當然他們之間還是有聯系的)。對於一個名為OuterClass的外圍類和一個名為InnerClass的內部類,在編譯成功后,會出現這樣兩個class文件:OuterClass.class和OuterClass$InnerClass.class。
在Java中內部類主要分為靜態內部類、成員內部類、局部內部類、匿名內部類。
靜態內部類
靜態內部類: 一般也稱”靜態嵌套類“,在類中用static
聲明的內部類。
因為是static,所以不依賴於外圍類對象實例而獨立存在,靜態內部類的可以訪問外圍類中的所有靜態成員,包括private
的靜態成員,但是它不能使用任何外圍類的非static成員變量和方法。
同時靜態內部類可以說是所有內部類中獨立性最高的內部類,其創建對象、繼承(實現接口)、擴展子類等使用方式與外圍類並沒有多大的區別。
public class OuterClass {//外圍類 public int aa; //實例成員 private static float f = 1.5f;//private的靜態成員 static void println() { System.out.println("這是靜態方法"); } protected static class StaticInnerClass{//protected的靜態內部類 float a; public StaticInnerClass() { a = f;// 外圍類的private靜態變量 println();//外圍類的靜態方法 } } } class OtherClass{ public static void main(String[] args) { //創建靜態內部類的對象 OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass(); } }
成員內部類
成員內部類: 定義在類的內部,而且與成員方法、成員變量同級,即也是外圍類的成員之一,因此 成員內部類 與 外圍類 是緊密關聯的。
注意:
這種緊密關聯指的是,成員內部類的對象的創建必須依賴於外圍類的對象(即沒有外圍類對象,就不可能創建成員內部類)。因此,成員內部類有以下3個特點:
-
成員內部類可以訪問外圍類的所有成員,包括私有成員;
-
成員內部類是不可以聲明靜態成員(包括靜態變量、靜態方法、靜態成員類、嵌套接口),但有個例外---可以聲明
static final
的變量, 這是因為編譯器對final
類型的特殊處理,是直接將值寫入字節碼; -
成員內部類對象都隱式地保存了一個引用,指向創建它的外部類對象;或者說,成員內部類的入口是由外圍類的對象保持着(靜態內部類的入口,則直接由外圍類保持着)
成員內部類中的 this,new關鍵字:
-
獲取外部類對象:
OuterClass.this
-
明確指定使用外部類的成員(當內部類與外部類的名字沖突時):
OuterClass.this
.成員名 -
創建內部類對象的new:
外圍類對象.new
//先創建外圍類對象 OuterClass outer=new OuterClass(); //創建成員內部類對象 OuterClass.InnerClass inner=outer.new InnerClass();
成員內部類的對象創建
我們知道,成員內部類就像外圍類的實例成員一樣,一定要存在對象才能訪問,即成員內部類必須綁定一個外圍類的對象。上面已經介紹了成員內部類的創建格式了,我們直接看一個例子
public class OuterClass {//外圍類 public int aa; //實例成員 private static float f = 1.5f;//private的靜態成員 public void initInnerClass() { System.out.println("內部類的初始化方法"); } public void createInnerClass() {// //外圍類的成員方法中創建成員內部類對象 InnerClass innerClass = new InnerClass(); } class InnerClass{//成員內部類 private double aa; //與圍類的變量aa的名字重復 public InnerClass(){ this.aa = OuterClass.this.aa + f;//明確指定兩個aa的所屬 initInnerClass(); } } } //其他類 class OtherClass{ public static void main(String[] args) { //其他類中創建成員內部類 OuterClass oc = new OuterClass();//外部類對象 //創建內部類對象 OuterClass.InnerClass innerClass = oc.new InnerClass(); } }
補充幾點:
-
成員內部類可以繼續包含成員內部類,而且不管一個內部類被嵌套了多少層,它都能透明地訪問它的所有外部類所有成員;
-
成員內部可以繼續嵌套多層的成員內部類,但無法嵌套靜態內部類;靜態內部類則都可以繼續嵌套這兩種內部類。
class InnerClass{//成員內部類 private double aa; //與圍類的變量aa的名字重復 public InnerClass(){ this.aa = OuterClass.this.aa + f;//明確指定兩個aa的所屬 initInnerClass(); } public class InnerInnerCalss2{//成員內部類中的成員內部類 protected double aa = OuterClass.this.aa;//最外層的外圍類的成員變量 }//InnerInnerCalss2 }//InnerClass
繼承成員內部類
在內部類的訪問權限允許的情況下,成員內部類也是可以被繼承的。由於成員內部類的對象依賴於外圍類的對象,或者說,成員內部類的構造器入口由外圍類的對象把持着。因此,繼承了成員內部類的子類必須要與一個外圍類對象關聯起來。同時,子類的構造器是必須要調用父類的構造器方法,所以也只能通過父類的外圍類對象來調用父類構造器。
下面的例子也是基於上面的例子的,只貼出多出的部分代碼。
class ChildClass extends OuterClass.InnerClass{ //成員內部類的子類的構造器的格式 public ChildClass(OuterClass outerClass) { outerClass.super();//通過外圍類的對象調用父類的構造方法 } }
局部內部類
局部內部類: 就是在方法、構造器、初始化塊中聲明的類,在結構上類似於一個局部變量。因此局部內部類是不能使用訪問修飾符。
局部內部類的兩個訪問限制:
-
對於局部變量,局部內部類只能訪問
final
的局部變量。不過,后期JDK(忘了是JDK幾了)局部變量可不用final修飾,也可以被局部內部類訪問,但你必須時刻記住此局部變量已經是final了,不能再改變。 -
對於類的全局成員,局部內部類定義在實例環境中(構造器、對象成員方法、實例初始化塊),則可以訪問外圍類的所有成員;但如果內部類定義在靜態環境中(靜態初始化塊、靜態方法),則只能訪問外圍類的靜態成員。
public class OuterClass { private int a = 21; static {//靜態域中的局部內部類 class LocalClass1{ // int z = a; //錯誤,在靜態的作用域中無法訪問對象成員 } } {//實例初始化塊中的局部內部類 class localClass2{ } } public OuterClass(){ int x = 2; final int y = 3; // x = 3;//若放開此行注釋,編譯無法通過,因為局部變量x已經是final類型 //構造器中的局部內部類 class localClass3{ int z = y; //可以訪問final的局部變量 int b = a;//可以訪問類的所有成員 //訪問沒有用final修飾的局部變量 int c = x; } } public void createRunnable() { final int x = 4; //方法中的局部內部類 class LocalClass4 implements Runnable {// @Override public void run() { System.out.println("局部final變量:"+x); System.out.println("對象成員變量:"+a); } } } }
匿名內部類
匿名內部類: 與局部內部類很相似,只不過匿名內部類是一個沒有給定名字的內部類,在創建這個匿名內部類后,便會立即用來創建並返回此內部類的一個對象引用。
作用:匿名內部類用於隱式繼承某個類(重寫里面的方法或實現抽象方法)或者實現某個接口。
匿名內部類的訪問限制: 與局部內部類一樣,請參考局部內部類;
匿名內部類的優缺點:
優點: 編碼方便快捷;
缺點:
-
只能繼承一個類或實現一個接口,不能再繼承其他類或其他接口。
-
只能用於創建一次對象實例;
下面的例子是我們創建線程時經常用到的匿名內部類的方式來快速地創建一個對象的例子:
class MyOuterClass { private int x = 5; void createThread() { final int a = 10; int b = 189; // 匿名內部類繼承Thread類,並重寫Run方法 Thread thread = new Thread("thread-1") { int c = x; //訪問成員變量 int d = a; //final的局部變量 int e = b; //訪問沒有用final修飾的局部變量 @Override public void run() { System.out.println("這是線程thread-1"); } }; // 匿名內部類實現Runnable接口 Runnable r = new Runnable() { @Override public void run() { System.out.println("線程運行中"); } }; } }