2018年11月26日 星期一

Java SE 入門教學 - 內部類別-巢狀類別

更新時間:11/26/2018

前言

如果要把每個功能都分出來,一個大型專案肯定會使用超多的類別,但,有時候這個類別只有特定的類別使用時,可以考慮使用另一個技術「內部類別(Inner Class)」、「巢狀類別(Nested Class)」。

內部類別通常是用來輔助外部類別,另一層封裝的類別階層組織,以提高程式的可讀性和維護性。



一、內部類別的基礎概念

在類別中您還可以定義類別,稱之為內部類別(Inner class)或「巢狀類別」(Nested class)。非 "static" 的內部類別可以分為三種:成員內部類別(Member inner class)、區域內部類別(Local inner class)與匿名內部類別(Anonymous inner class);static 的內部類別只有一種:靜態成員內部類別。

使用內部類別的好處在於可以直接存取外部類別的私用(private)成員,舉個例子來說,在視窗程式中,您可以使用內部類別來實作一個事件傾聽者類別,這個視窗傾聽者類別可以直接存取視窗元件,而不用透過參數傳遞。

另一個好處是,當某個 Slave 類別完全只服務於一個 Master 類別時,我們可以將之設定為內部類別,如此使用 Master 類別的人就不用知道 Slave 的存在。

存取修飾子的使用:

外部類別只能使用:public、default、final、abstract。
內部類別可以使用:private、protected、public、static。



二、非靜態的成員內部類別

非靜態的成員內部類別以下簡稱非靜態內部類別,在其內不可以宣告靜態變數與方法。

建立非靜態內部類別物件時,必須先建立外部類別物件,語法如下:

外部類別 物件變數1 = new 外部類別建構子()
外部類別.內部類別 物件變數2 = 物件變數1.new 內部類別建構子()

當建立非靜態內部類別物件時,JVM 會產生兩個隱藏變數「this」與「外部類別名稱.this」,如此可以呼叫到外部類別的變數與方法。

學會 存取外部類別的屬性變數、內部類別的屬性變數、區域變數 與 全域變數,利用「this」與「外部類別名稱.this」來達成操作。


執行成功 1

Inner2 類別有兩個隱藏變數,this 指向 Inner2 物件實例,Outer2.this 指向 Outer2 物件實例。

▼ 記憶體空間配置


編譯錯誤 1

TestInner1.java:3: error: Illegal static declaration in inner class Outer1.Inner1
                static int x=100;
                           ^
  modifier 'static' is only allowed in constant variable declarations
1 error


三、靜態的成員內部類別

靜態的成員內部類別以下簡稱靜態內部類別,靜態與非靜態的變態或方法都可以宣告。

靜態內部類別中,如果沒有建立外部類別物件,就使用到外部類別的屬性數變或方法時,編譯會出錯。

靜態內部類別中的靜態方法內,如果使用到內部非靜態區域變數時,編譯會出錯。因為 JVM 在配置靜態的東西時,會與非靜態的記憶體區塊不同,所以會無法找到此非靜態區域變數。

使用靜態內部類別的語法如下:

外部類別.內部類別 物件參考變數 = new 外部類別.內部類別建構子();

執行成功 2


編譯錯誤 2

靜態內部類別有一個非靜態屬性變數 a,當 JVM 配置記憶體時,靜態內部類別會先建立起來,包含靜態屬性變數與方法,但非靜態屬性變數 a 並不會建立,所以 method2() 的記憶體內找不到非靜態屬性變數 a。

TestInner4.java:5: error: non-static variable this cannot be referenced from a static context
                        System.out.println("內部屬性變數 a : " + this.a);
                                                           ^
1 error

編譯錯誤 3

因為靜態內部類別沒有建立外部物件,所以無法取得外部物件實例。

TestInner5.java:6: error: non-static variable this cannot be referenced from a static context
                        System.out.println("外部屬性變數 a : " + Outer5.this.a);
                                                                 ^
1 error


四、區域內部類別

區域內部類別也稱為方法內的內部類別。因為只能在方法內才有作用,故實用性不高。語法如下:

class 外部類別 {
  [存取修飾子] 傳回值型別 方法名稱(參數) {
    class 內部類別 {
      ...
    }
    ...
    內部類別 物件參考變數 = new 內部類別建構子();
  }
  ...
}

執行成功 3


編譯錯誤 4

方法內的內部類別如果要使用區域變數時,必須宣告成 final 或 其值不改變才能夠使用。

TestInner7.java:8: error: local variables referenced from an inner class must be final or effectively final
                                System.out.println("a = " + (a++));
                                                             ^
1 error


五、匿名內部類別

這個觀念與上一篇 匿名物件 有點類似,但實際重點在於「匿名內部類別」,也就是說這個類別是沒有名字的。我們探討一下語法的演變:

一般介面類別必須由另一個類別繼承且實作後,才能建立物件

interface(or abstract or class) 介面名稱(or 抽象類別名稱 or 類別名稱){

}

class 類別名稱 implements(or extends) 介面名稱(抽象類別名稱 or 類別名稱){

}

建立物件的語法: 類別名稱 物件參考變數 = new 建構子();

把上方建立物件的語法改變一下

類別名稱 → 父類別的介面名稱(or 抽象類別名稱 or 類別名稱)
物件參考變數 保留
建構子() → 後面加上子類別{}全部內容
分號; 保留

語法就變成如下所示:

介面(or 類別) 物件參考變數 = new 建構子(){
  ...
  (實作介面中的所有抽象方法;)
  ...
};

此概念是在介面和抽象類別應用上發展起來的,因為這個類別是沒有名字的,所以也只能建構一次,如果經常會有臨時繼承某個類別或實作某個介面並建立實例的需求時,可以使用。


執行成功 4:匿名內部類別的物件


執行成功 5:匿名內部類別的匿名物件



六、總結

如果一個類別只服務一個類別時,我們可以把它變成內部類別(Inner class)。

非靜態內部類別:

(i) 成員內部類別(Member inner class),也稱為內部類別。
(ii) 區域內部類別(Local inner class),也稱為方法內部類別。
(iii) 匿名內部類別(Anonymous inner class),也稱為匿名類別。

靜態內部類別:

(i) 靜態成員內部類別(Static Member inner class),也稱為靜態內部類別。





1 則留言:

  1. 有個疑惑是,為何靜態內部類別無法像非靜態內部類別一樣產生物件呢?
    以你的例子來說就是
    outClass.Inner3 inObj = new Outer3().new Inner3();
    為何會編譯錯誤呢?

    回覆刪除