深刻理解Java:內部類

1、什麼是內部類?

  內部類是指在一個外部類的內部再定義一個類。內部類做爲外部類的一個成員,而且依附於外部類而存在的。內部類可爲靜態,可用protected和private修飾(而外部類只能使用public和缺省的包訪問權限)。內部類主要有如下幾類:內部、局部內部、靜內部、匿名內部java

2、內部類的共性

(1)、內部類仍然是一個獨立的類,在編譯以後內部類會被編譯成獨立的.class文件,可是前面冠之外部類的類名和$符號 。android

(2)、內部類不能用普通的方式訪問。app

(3)、內部類聲明成靜態的,就不能隨便的訪問外部類的成員變量了,此時內部類只能訪問外部類的靜態成員變量 。ide

(4)、外部類不能直接訪問內部類的的成員,但能夠經過內部類對象來訪問函數

 

  內部類是外部類的一個成員,所以內部類能夠自由地訪問外部類的成員變量,不管是不是private的。學習

  由於當某個外圍類的對象建立內部類的對象時,此內部類會捕獲一個隱式引用,它引用了實例化該內部對象的外圍類對象。經過這個指針,能夠訪問外圍類對象的所有狀態。ui

經過反編譯內部類的字節碼,分析以後主要是經過如下幾步作到的: 
  1 編譯器自動爲內部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用; 
  2 編譯器自動爲內部類的構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數爲1中添加的成員變量賦值; 
  3 在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。this

3、爲何須要內部類?

其主要緣由有如下幾點:spa

  • 內部類方法能夠訪問該類定義所在的做用域的數據,包括私有的數據.net

  • 內部類能夠對同一個包中的其餘類隱藏起來,通常的非內部類,是不容許有 private 與protected權限的,但內部類能夠

  • 能夠實現多重繼承

  • 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名內部類比較便捷

使用內部類最吸引人的緣由是:

  每一個內部類都能獨立地繼承自一個(接口的)實現,因此不管外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。你們都知道Java只能繼承一個類,它的多重繼承在咱們沒有學習內部類以前是用接口來實現的。但使用接口有時候有不少不方便的地方。好比咱們實現一個接口就必須實現它裏面的全部方法。而有了內部類就不同了。它可使咱們的類繼承多個具體類或抽象類。

你們看下面的例子:

public class Example1 { public String name(){ return "liutao"; } } public class Example2 { public int age(){ return 25; } } public class MainExample { private class test1 extends Example1{ public String name(){ return super.name(); } } private class test2 extends Example2 { public int age(){ return super.age(); } } public String name(){ return new test1().name(); } public int age(){ return new test2().age(); } public static void main(String args[]){ MainExample mi=new MainExample(); System.out.println("姓名:"+mi.name()); System.out.println("年齡:"+mi.age()); } }

4、成員內部類:

  即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,能夠與普通成員同樣進行修飾和限制。成員內部類不能含有static的變量和方法。

public class Outer { private static int i = 1; private int j = 10; private int k = 20; public static void outer_f1() {} public void outer_f2() {} // 成員內部類中,不能定義靜態成員 // 成員內部類中,能夠訪問外部類的全部成員
    class Inner { // static int inner_i = 100;//內部類中不容許定義靜態變量
        int j = 100; // 內部類和外部類的實例變量能夠共存
        int inner_i = 1; void inner_f1() { System.out.println(i); // 在內部類中訪問內部類本身的變量直接用變量名
 System.out.println(j); // 在內部類中訪問內部類本身的變量也能夠用this.變量名
            System.out.println(this.j); // 在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
            System.out.println(Outer.this.j); // 若是內部類中沒有與外部類同名的變量,則能夠直接用變量名訪問外部類變量
 System.out.println(k); outer_f1(); outer_f2(); } } // 外部類的非靜態方法訪問成員內部類
    public void outer_f3() { Inner inner = new Inner(); inner.inner_f1(); } // 外部類的靜態方法訪問成員內部類,與在外部類外部訪問成員內部類同樣
    public static void outer_f4() { // step1 創建外部類對象
        Outer out = new Outer(); // step2 根據外部類對象創建內部類對象
        Inner inner = out.new Inner(); // step3 訪問內部類的方法
 inner.inner_f1(); } public static void main(String[] args) { //outer_f4();//該語句的輸出結果和下面三條語句的輸出結果同樣 // 若是要直接建立內部類的對象,不能想固然地認爲只需加上外圍類Outer的名字, // 就能夠按照一般的樣子生成內部類的對象,而是必須使用此外圍類的一個對象來 // 建立其內部類的一個對象: // Outer.Inner outin = out.new Inner() // 所以,除非你已經有了外圍類的一個對象,不然不可能生成內部類的對象。由於此 // 內部類的對象會悄悄地連接到建立它的外圍類的對象。若是你用的是靜態的內部類, // 那就不須要對其外圍類對象的引用。
        Outer out = new Outer(); Outer.Inner outin = out.new Inner(); outin.inner_f1(); } }

6、局部內部類:

  在方法中定義的內部類稱爲局部內部類。與局部變量相似,局部內部類不能有訪問說明符,由於它不是外圍類的一部分,可是它能夠訪問當前代碼塊內的常量,和此外圍類全部的成員。

須要注意的是:

  (1)、局部內部類只能在定義該內部類的方法內實例化,不能夠在此方法外對其實例化。

  (2)、局部內部類對象不能使用該內部類所在方法的非final局部變量。

具體緣由等下再說

public class Outer { private int s = 100; private int out_i = 1; public void f(final int k) { final int s = 200; int i = 1; final int j = 10; // 定義在方法內部
        class Inner { int s = 300;// 能夠定義與外部類同名的變量 // static int m = 20;//不能夠定義靜態變量
            Inner(int k) { inner_f(k); } int inner_i = 100; void inner_f(int k) { // 若是內部類沒有與外部類同名的變量,在內部類中能夠直接訪問外部類的實例變量
 System.out.println(out_i); // 能夠訪問外部類的局部變量(即方法內的變量),可是變量必須是final的
 System.out.println(j); // System.out.println(i); // 若是內部類中有與外部類同名的變量,直接用變量名訪問的是內部類的變量
 System.out.println(s); // 用this.變量名訪問的也是內部類變量
                System.out.println(this.s); // 用外部類名.this.內部類變量名訪問的是外部類變量
                System.out.println(Outer.this.s); } } new Inner(k); } public static void main(String[] args) { // 訪問局部內部類必須先有外部類對象
        Outer out = new Outer(); out.f(3); } }

 

7、靜態內部類(嵌套類):

  若是你不須要內部類對象與其外圍類對象之間有聯繫,那你能夠將內部類聲明爲static。這一般稱爲嵌套類(nested class)。想要理解static應用於內部類時的含義,你就必須記住,普通的內部類對象隱含地保存了一個引用,指向建立它的外圍類對象。然而,當內部類是static的時,就不是這樣了。嵌套類意味着:

  1. 要建立嵌套類的對象,並不須要其外圍類的對象。

  2. 不能從嵌套類的對象中訪問非靜態的外圍類對象。

public class Outer { private static int i = 1; private int j = 10; public static void outer_f1() {} public void outer_f2() {} // 靜態內部類能夠用public,protected,private修飾 // 靜態內部類中能夠定義靜態或者非靜態的成員
    private static class Inner { static int inner_i = 100; int inner_j = 200; static void inner_f1() { // 靜態內部類只能訪問外部類的靜態成員(包括靜態變量和靜態方法)
            System.out.println("Outer.i" + i); outer_f1(); } void inner_f2() { // 靜態內部類不能訪問外部類的非靜態成員(包括非靜態變量和非靜態方法) // System.out.println("Outer.i"+j); // outer_f2();
 } } public void outer_f3() { // 外部類訪問內部類的靜態成員:內部類.靜態成員
 System.out.println(Inner.inner_i); Inner.inner_f1(); // 外部類訪問內部類的非靜態成員:實例化內部類便可
        Inner inner = new Inner(); inner.inner_f2(); } public static void main(String[] args) { new Outer().outer_f3(); } }

生成一個靜態內部類不須要外部類成員:這是靜態內部類和成員內部類的區別。靜態內部類的對象能夠直接生成:Outer.Inner in = new Outer.Inner();而不須要經過生成外部類對象來生成。這樣實際上使靜態內部類成爲了一個頂級類(正常狀況下,你不能在接口內部放置任何代碼,但嵌套類能夠做爲接口的一部分,由於它是static 的。只是將嵌套類置於接口的命名空間內,這並不違反接口的規則)

 

8、匿名內部類:

簡單地說:匿名內部類就是沒有名字的內部類。什麼狀況下須要使用匿名內部類?若是知足下面的一些條件,使用匿名內部類是比較合適的:
  • 只用到類的一個實例。
  • 類在定義後立刻用到。
  • 類很是小(SUN推薦是在4行代碼如下)
  • 給類命名並不會致使你的代碼更容易被理解。
在使用匿名內部類時,要記住如下幾個原則:
  •   匿名內部類不能有構造方法。

  •   匿名內部類不能定義任何靜態成員、方法和類。

  •   匿名內部類不能是public,protected,private,static。

  •   只能建立匿名內部類的一個實例。

  •    一個匿名內部類必定是在new的後面,用其隱含實現一個接口或實現一個類。

  •   因匿名內部類爲局部內部類,因此局部內部類的全部限制都對其生效。

 

下面的代碼展現的是,若是你的基類須要一個有參數的構造器,應該怎麼辦:
public class Parcel7 {  public Wrapping wrap(int x) {  // Base constructor call:
 return new Wrapping(x) { // Pass constructor argument.
 public int value() {  return super.value() * 47; } }; // Semicolon required
 }  public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } }
只需簡單地傳遞合適的參數給基類的構造器便可,這裏是將x 傳進new Wrapping(x)。在匿名內部類末尾的分號,並非用來標記此內部類結束(C++中是那樣)。實際上,它標記的是表達式的結束,只不過這個表達式正巧包含了內部類罷了。所以,這與別的地方使用的分號是一致的。
 
若是在匿名類中定義成員變量或者使用帶參數的構造函數,你一樣可以對其執行初始化操做:
public class Parcel8 { // Argument must be final to use inside // anonymous inner class:
    public Destination dest(final String name, String city) { return new Destination(name, city) { private String label = name; public String getName() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania", "gz"); } abstract class Destination { Destination(String name, String city) { System.out.println(city); } abstract String getName(); } }
注意這裏的形參city,因爲它沒有被匿名內部類直接使用,而是被抽象類Inner的構造函數所使用,因此沒必要定義爲final。
 
 

9、內部類的重載問題

 
  若是你建立了一個內部類,而後繼承其外圍類並從新定義此內部類時,會發生什麼呢?也就是說,內部類能夠被重載嗎?這看起來彷佛是個頗有用的點子,可是「重載」內部類就好像它是外圍類的一個方法,其實並不起什麼做用:
 
class Egg { private Yolk y; protected class Yolk { public Yolk() { System.out.println("Egg.Yolk()"); } } public Egg() { System.out.println("New Egg()"); y = new Yolk(); } } public class BigEgg extends Egg { public class Yolk { public Yolk() { System.out.println("BigEgg.Yolk()"); } } public static void main(String[] args) { new BigEgg(); }
}
輸出結果爲:
New Egg()
Egg.Yolk()
 
缺省的構造器是編譯器自動生成的,這裏是調用基類的缺省構造器。你可能認爲既然建立了BigEgg 的對象,那麼所使用的應該是被「重載」過的Yolk,但你能夠從輸出中看到實際狀況並非這樣的。
這個例子說明,當你繼承了某個外圍類的時候,內部類並無發生什麼特別神奇的變化。這兩個內部類是徹底獨立的兩個實體,各自在本身的命名空間內。固然,明確地繼承某個內部類也是能夠的:
class Egg2 { protected class Yolk { public Yolk() { System.out.println("Egg2.Yolk()"); } public void f() { System.out.println("Egg2.Yolk.f()"); } } private Yolk y = new Yolk(); public Egg2() { System.out.println("New Egg2()"); } public void insertYolk(Yolk yy) { y = yy; } public void g() { y.f(); } } public class BigEgg2 extends Egg2 { public class Yolk extends Egg2.Yolk { public Yolk() { System.out.println("BigEgg2.Yolk()"); } public void f() { System.out.println("BigEgg2.Yolk.f()"); } } public BigEgg2() { insertYolk(new Yolk()); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g(); } }
輸出結果爲:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk.f()
 
如今BigEgg2.Yolk 經過extends Egg2.Yolk 明確地繼承了此內部類,而且重載了其中的方法。Egg2 的insertYolk()方法使得BigEgg2 將它本身的Yolk 對象向上轉型,而後傳遞給引用y。因此當g()調用y.f()時,重載後的新版的f()被執行。第二次調用Egg2.Yolk()是BigEgg2.Yolk 的構造器調用了其基類的構造器。能夠看到在調用g()的時候,新版的f()被調用了。
 

10、內部類的繼承問題

  由於內部類的構造器要用到其外圍類對象的引用,因此在你繼承一個內部類的時候,事情變得有點複雜。問題在於,那個「祕密的」外圍類對象的引用必須被初始化,而在被繼承的類中並不存在要聯接的缺省對象。要解決這個問題,需使用專門的語法來明確說清它們之間的關聯:
class WithInner { class Inner { Inner(){ System.out.println("this is a constructor in WithInner.Inner"); }; } } public class InheritInner extends WithInner.Inner { // ! InheritInner() {} // Won't compile
 InheritInner(WithInner wi) { wi.super(); System.out.println("this is a constructor in InheritInner"); } public static void main(String[] args) { WithInner wi = new WithInner(); InheritInner ii = new InheritInner(wi); } }
輸出結果爲:
this is a constructor in WithInner.Inner
this is a constructor in InheritInner
 
能夠看到,InheritInner 只繼承自內部類,而不是外圍類。可是當要生成一個構造器時,缺省的構造器並不算好,並且你不能只是傳遞一個指向外圍類對象的引用。此外,你必須在構造器內使用以下語法:
enclosingClassReference.super();
這樣才提供了必要的引用,而後程序才能編譯經過。
 

爲何非靜態內部類中不能有static修飾的屬性,但卻能夠有常量?

如:

public class InnerClassDemo{ int x; class A{ static  int a = 0;//這樣寫是不合法的.
        static final int b=0;//這樣寫是合法的
 } }

定義一個靜態的域或者方法,要求在靜態環境或者頂層環境,即若是加上 static class A變成靜態內部類就ok非靜態內部類 依賴於一個外部類對象,而靜態域/方法是不依賴與對象——僅與類相關(細說了,就是加載靜態域時,根本沒有外部類對象)所以,非靜態內部類中不能定義靜態域/方法,編譯過不了。


而常量之因此能夠(不論有無static),由於java在編譯期就肯定全部常量,放到所謂的常量池當中。常量的機制和普通變量不同

爲何匿名內部類和局部內部類只能訪問final變量

參考:

java內部類總結 

【解惑】領略Java內部類的「內部」

相關文章
相關標籤/搜索