一個定義在另外一個類中的類,叫做內部類java
內部類容許你把一些邏輯相關的類組織在一塊兒,並控制位於內部的類的可見性,這麼看來,內部類就像是一種代碼隱藏機制:將類置於其餘類的內部,從而隱藏名字與組織代碼的模式。程序員
建立內部類的方式就如同你想的同樣,把類的定義置於外部類裏面閉包
public class Outer { // 其餘一些成員聲明 ... public class Inner { // 其餘一些成員聲明 ... } }
內部類與其外部類之間存在一種聯繫,經過這個聯繫能夠訪問外部類的全部成員,而不須要任何特殊條件,能夠這麼說,內部類擁有其外部類的全部元素的訪問權。ide
這是如何作到的呢?當某個外部類的對象建立一個內部類對象時,該內部類對象會祕密地捕獲一個指向外部類對象的引用。在你訪問外部類成員時,能夠經過這個引用選擇外部類的成員。全部這些實現的細節由編譯器幫忙處理,大多數時候都無需程序員關心。this
若是你須要生成對外部類對象的引用,可使用外部類的名字後面緊跟圓點和 thiscode
public class Outer { public class Inner { public Outer getOuter() { return Outer.this; } } public static void main(String[] args) { Outer outer = new Outer(); Outer outer2 = outer.getOuter(); } }
若是你想建立某個內部類對象,必須在 new 表達式中提供其外部類的引用,這時須要使用 .new 語法對象
public class Outer { public class Inner {} public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); } }
若是從外部類的非靜態方法以外的位置建立某個內部類對象,要具體指明這個內部類對象的類型:OuterClassName.InnerClassName
(在外部類的靜態方法中也能夠直接指明類型 InnerClassName
)繼承
當將內部類向上轉型爲其基類(接口),能夠很方便地隱藏實現細節,由於外界所獲得的只是指向基類或接口的引用。接口
// 這是一個接口 public interface Content {...} class Outer { // 注意這裏將訪問修飾符設爲 private private class PContent implements Content { ... } public Content getContent() { return new PContent(); } }
在 Outer 類中,內部類 PContent 是私有的,除了 Outer 外沒有人能夠訪問。這意味着,客戶端程序員若是想了解這些內部類成員,那是要受限制的。客戶端程序員沒法訪問任何新增的、不屬於公共接口的方法,也不能向下轉型,由於不能訪問其名字。作用域
不管一個內部類被嵌套多少層,它都能透明地訪問全部它所嵌入的外部類的全部成員
class MNA { private void f() {} class A { private void g(){} public class B { void h() { g(); f(); } } } } public class TestClass { public static void main(String[] args) { MNA mna = new MNA(); MNA.A mnaa = mna.new A(); MNA.A.B mnaab = mnaa.new B(); mnaab.h(); } }
成員內部類是最普通的內部類,它和外部類的成員變量同樣,定義在類的內部,能夠聲明訪問修飾符。成員內部類有以下特性:
OuterClassName.this.innerClassMember
在方法的做用域內(而不是在其餘類的做用域內)建立一個完整的類,這個類被稱做局部內部類。因爲是定義在方法內,因此不能聲明訪問修飾符,做用範圍僅在聲明類的代碼塊中,但這並不意味着一旦方法做用域執行完畢,局部內部類就不可用了。局部內部類有以下特性:
public class Outer { public Outer method() { class Inner { ... } } }
只用一次的沒有名字的類,具體看下面的例子:
public class Outer { public Content method() { return new Content() { private int i = 11; @Override public int getValue() { return i } } } }
這種奇怪的語法指的是:建立一個繼承自 Content 的匿名類的對象。經過 new 表達式返回的這個引用被自動向上轉型爲 Content 的引用。上述匿名內部類是下述形式的一種簡化形式,也就是說,匿名內部類能夠看做對成員內部類或局部內部類的一種簡寫:
public class Outer { class MyContent implements Content { private int i = 11; @Override public int getValue() { return i } } public Content method() { return new MyContent(); } }
匿名內部類沒有構造器(由於它根本沒有名字),若是咱們但願爲匿名內部類作一些初始化操做,那該怎麼辦呢?若是該匿名內部類的基類構造器有參數,則能夠直接使用,但編譯器會要求其參數引用是 final(即便你不加,Java8 也會爲咱們自動加上 final)。或者經過代碼塊,模仿構造器的行爲來實現初始化。
public class Outer { public Content method() { return new Content(final int a, final int b) { private int i = a; private int j; { j = b; } } } }
若是不須要內部類對象與其外部類對象之間有聯繫,能夠將內部類聲明爲 static。既然靜態內部類不須要與外部類有聯繫,那麼也就沒有指向外部類的 this 引用,更像一個獨立的類,這意味着:
public class Outer { public static class Inner { private String label; public String method1() { ... } static int x = 10; public static void method2() { ... } } }
靜態內部類能夠做爲接口的一部分,由於你放到接口中的任何類都自動是 public 和 static 的,甚至能夠在內部類中實現其外部類接口。
public interface ClassInterface { void method1(); class InnerClass implements ClassInterface { @Override public void method1() { ... } } }
內部類的第一個用途就是能夠操做建立它的外部類的對象,典型的例子就是集合中常使用的迭代器 iterator。這只是其中之一,使用內部類最吸引人的緣由莫過於:
每一個內部類都能獨立地繼承自某個類或實現某個接口,不管外部類是否繼承了某個類或實現某個接口
基於這個特性,使得多重繼承成爲可能,咱們可讓多個內部類以不一樣的方式實現不一樣的接口,或繼承不一樣的類。下面這個例子使用了匿名內部類,可能大家已經習覺得常,但別忘了匿名內部類其實就是內部類的一種簡寫形式。
class D {} abstract class E {} class Z extend D { E getE() { return new E() {}; } } public class TestClass { static void setD(D d){} static void setE(E e){} public static void main(String[] args) { Z z = new Z(); setD(z); setE(z.getE); } }
另外,內部類也是實現回調的絕佳選擇。經過內部類能夠實現一個閉包,所謂閉包就是一個可調用的對象,它記錄了一些信息。在須要提供閉包來實現回調時,咱們能夠採用匿名內部類的方式。
由於內部類的構造器必須鏈接到其外部類對象的引用,因此在繼承內部類時,那個指向外部類對象的引用必須被初始化,而在派生類中又再也不存在這個引用了。要解決這個問題,必須使用特殊的語法來明確說清它們之間的關係。
class WithInner { class Inner {} } public class InheritInner extends WithInner.Inner { InheritInner(WithInner wi) { wi.super(); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner li = new InheritInner(wi); } }
構造器要傳遞一個指向外部類對象的引用,此外,還必須顯式地調用基類構造器。
內部類能夠被覆蓋嗎?答案是不能。若是建立一個內部類,而後繼承其外部類並從新定義此內部類,並不會覆蓋該內部類。兩個內部類是徹底獨立的,各自在本身的命名空間。
每一個類編譯後都會產生一個 .class 文件,內部類也會生成一個 .class 文件,但其命名有嚴格的規則:外部類的名字 + $ + 內部類的名字,例如 OuterClass.java 生成的 .class 文件包括:
OuterClass.class OuterClass$InnerClass.class
若是內部類是匿名的,編譯器會簡單地生成一個數字做爲其標識符。若是內部類嵌套在別的內部類之中,只需直接將它們的名字在外部類標識符與 $ 的後面。