基礎
Java支持類中嵌套類,稱之為nested class。嵌套的層數沒有限制,但實際中一般最多用兩層。根據內部類是否有static修飾,分為 static nested class和 non-static nested class。non-static nested class又被稱為 inner class。inner class裡面又有兩個特殊一點的類: local class和 anonymous class。特殊之處主要在於語法使用上,實質功能是差不多的。 官方 是這樣解釋的:
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.用圖表示分類及關係如下:
上面是按照官方的說法來分的,實際中很多人習慣把所有的嵌套類都稱為inner class(內部類),這只是個稱謂,不影響溝通就好。後文用nested class來統稱static nested class和non-static nested class。各個類的主要特性和限制已經在圖中說明了(適用於JDK 8及以後)。
那為什麼需要內部類呢?內部類是在Java 1.1中引入的,當時很多人質疑該設計增加了Java的複雜性,但實用性不強,當然這種問題仁者見仁智者見智。官方的解釋是這樣的:
Why Use Nested Classes?
Compellingreasons for using nested classes include the following:
It is a way of logically grouping classes that are only used in one place : If a class is useful to only one other class, then it is logical to embed it in that class and keep the two together. Nesting such "helper classes" makes their package more streamlined.**It increases encapsulation : Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.It can lead to more readable and maintainable code : Nesting small classes within top-level classes places the code closer to where it is used.總的來說,嵌套類最大的目的是改善代碼的組織,並不是必不可少的功能。嵌套類能實現的功能,通過正常的類都可以實現,只不過可能要多寫點代碼,且不是很優雅而已。
為什麼inner class可以訪問外層類的非靜態成員
要說明的是: 嵌套類是對於編譯器而言的,對虛擬機來說沒有什麼嵌套類,只有正常的類 。也就是說嵌套類經過編譯器編譯之後要轉化成一個個正常的類,比如A類裡面嵌套了B類。那經過編譯器之後會變成兩個獨立的類: A 和 A$B 。這樣問題就很簡單了,B類要訪問A類的非靜態成員,要滿足兩個條件:
要有A類的實例。且要有訪問成員的權限或者方式。而編譯器在編譯期間就幫我們幹了這兩件事,下面驗證一下。
定義一個嵌套類:
public class OuterClass { /** * 定義一個公有成員變量 */ public Object publicVariable = "public member variable"; private Object privateVariable = "private member variable"; /** * 定義兩個私有成員變量 */ private Object privateVariable2 = "private member variable2"; /** * 定義一個私有成員方法 * @param parameter */ private void privateMethod(String parameter) { System.out.println(parameter); } /** * 調用inner class */ public void show() { InnerClass innerClass = new InnerClass(); innerClass.print(); } /** * inner class */ class InnerClass { void print() { // inner class裡面直接調用了外部類的私有成員變量和成員方法 System.out.println(privateVariable); privateMethod("invoke outer class private method."); // 調用外層類的公有變量 System.out.println(publicVariable); } } public static void main(String[] args) { new OuterClass().show(); }}上面代碼定義了外層類是 OuterClass ,內部類是 InnerClass 。外層類定義了1個公有變量、2個私有變量和1個私有方法。然後在InnerClass裡面直接使用了OuterClass的所有成員。程序運行結果如下:
private member variableinvoke outer class private method.public member variable反編譯一下上面的兩個類:
# javap -p OuterClass.class Compiled from "OuterClass.java"public class OuterClass { public java.lang.Object publicVariable; private java.lang.Object privateVariable; private java.lang.Object privateVariable2; public OuterClass(); private void privateMethod(java.lang.String); public void show(); public static void main(java.lang.String[]); # 注意這兩個靜態方法 static java.lang.Object access$000(OuterClass); static void access$100(OuterClass, java.lang.String);}# javap -p OuterClass\$InnerClass.classCompiled from "OuterClass.java"class OuterClass$InnerClass { # 注意這個final的成員變量和下面的構造函數 final OuterClass this$0; OuterClass$InnerClass(OuterClass); void print();}結論就是:
編譯器修改了內部類:增加了一個final的外層類實例作為內部類的成員變量;修改了內部類的構造函數,將外部類實例通過內部類的構造函數傳遞給內部類。這樣內部類就有了外部類的實例,上面的第1個條件就達成了。編譯器在外部類中增加了幾個非private的靜態方法。對於內部類訪問外部類的每一個私有成員,都會有這麼一個方法。這樣內部類就可以通過這些靜態方法去訪問外部類的私有成員了。非私有的成員直接通過1中的外層類實例即可訪問,所以就無需生成這些靜態方法了。再進一步驗證一下上面的結論1,當執行 InnerClass innerClass = new InnerClass(); 語句創建一個內部類實例之後,可以觀測到下面的結果:
可以看到,內部類實例( OuterClass$InnerClass@470 )自動引用了外層類實例( OuterClass@464 )。所以, inner class之所以能訪問外層類的成員是因為它在實例化的時候就已經和一個外層類的實例關聯了 ,實際是通過這個外層類實例去訪問外層類的成員。對於私有成員,還生成了一些輔助的靜態方法。這也說明,要實例化inner class,必須先實例化它的外層類。
另外有個限制就是inner class裡面不能定義靜態變量和靜態方法,一個例外是可以定義基礎類型和String類型的靜態常量。比如:
static final String s = 「s」; // OKstatic final int i = 5; // OKstatic final Integer ii = 5; // 錯誤local class和anonymous class都屬於特殊的inner class,所以上面講述的所有東西對他們也適用。
為什麼static nested class不能訪問外層類的非靜態成員
原因很簡單,static nested class除了被定義到某個類裡面以外,幾乎和普通的類沒有什麼區別。 它不會和外層類的某個實例關聯,比如我們在上面的OuterClass裡面再定義一個 StaticNestedClass :
static class StaticNestedClass { private int a; void foo() {}}反編譯以後:
# javap -p OuterClass\$StaticNestedClass.classCompiled from "OuterClass.java"class OuterClass$StaticNestedClass { private int a; OuterClass$StaticNestedClass(); void foo();}除了類名被改寫了以外,和原來定義的類沒有任何區別。所以如果沒有被定義為private的話,static nested class完全可以獨立於外層類使用。
所有nested class都可以訪問外層類的靜態成員
上面討論的都是nested class能不能訪問外層類的非靜態成員,那如果是靜態成員呢?結論就是所有nested class都可以訪問的靜態成員,不管是不是私有。原理的話和inner class訪問外層類非static成員是一樣的,如果是private的,編譯器會在外層中生成一個輔助訪問的static方法,如果是非私有的,那通過類就可以直接訪問。
## 如果nested class是private的?
我們知道正常的類是不能使用private和protected的(只能是public或者不加訪問修飾符),但nested class卻可以,因為nested class其實就是外層類的一個特殊成員,就像成員變量、成員方法一樣。比如,如果我們不想讓外部的其它類看到nested class的類,就可以將它設置成private的,但對於外層類是沒有影響的,照樣可以操作這個類。這個怎麼做到的呢?
我們將上面的StaticNestedClass改為private的:
private static class StaticNestedClass { void foo() { System.out.println(a); }}反編譯看下:
# javap -p OuterClass\$StaticNestedClass.classCompiled from "OuterClass.java"class OuterClass$StaticNestedClass { private OuterClass$StaticNestedClass(); void foo(); OuterClass$StaticNestedClass(OuterClass$1);}可以看到,如果nested class被設置成private,它原來的構造函數就會被設置為private的,同時編譯器又新增了一個外部可見的構造函數 OuterClass$StaticNestedClass(OuterClass$1) ,這個構造函數的一個入參就是外部類的實例。這樣,外部類實例化nested class的時候會先調用這個構造函數,這個構造函數內部又調用了原始的private的構造函數。inner class也是這樣的。
## 總結
嵌套類的實質就是外層類的成員,就像成員變量、成員方法一樣,初衷是提高代碼結構的緊湊和可維護性。 使用嵌套類的幾乎唯一的場景就是這個內部類僅供外層類(或者包含它的代碼塊)使用,其它場景都應該使用正常的一級類 。按這個思路使用即可,不要濫用,更不要搞騷操作。
Reference
Core Java Volume IOrace The Java Tutorials原文連結:https://niyanchun.com/java-nested-class.html
如果覺得本文對你有幫助,可以轉發關注支持一下