前言
如果要把每個功能都分出來,一個大型專案肯定會使用超多的類別,但,有時候這個類別只有特定的類別使用時,可以考慮使用另一個技術「內部類別(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。
二、非靜態的成員內部類別
非靜態的成員內部類別以下簡稱非靜態內部類別,在其內不可以宣告靜態變數與方法。
建立非靜態內部類別物件時,必須先建立外部類別物件,語法如下:
外部類別.內部類別 物件變數2 = 物件變數1.new 內部類別建構子()
當建立非靜態內部類別物件時,JVM 會產生兩個隱藏變數「this」與「外部類別名稱.this」,如此可以呼叫到外部類別的變數與方法。
學會 存取外部類別的屬性變數、內部類別的屬性變數、區域變數 與 全域變數,利用「this」與「外部類別名稱.this」來達成操作。
執行成功 1:
Inner2 類別有兩個隱藏變數,this 指向 Inner2 物件實例,Outer2.this 指向 Outer2 物件實例。

class Outer2{
private int a = 10;
private static int sx = 200;
public void method1(){
System.out.println("外部 method1()");
}
class Inner2{
int a = 20;
public void method1(){
int a = 30;
System.out.println("內部 method1()");
System.out.println("區域變數 a:" + a);
System.out.println("內部屬性變數 a:" + this.a);
System.out.println("外部屬性變數 a:" + Outer2.this.a);
System.out.println("全域變數 sx" + Outer2.sx); //變數名稱沒有重複可以直接使用sx。
}
}
}
class TestInner2{
public static void main(String[] arg){
Outer2 outObj = new Outer2();
outObj.method1();
System.out.println();
Outer2.Inner2 inObj = outObj.new Inner2();
inObj.method1();
System.out.println();
// 外部程式使用不到,可以使用暱名物件建立內部物件
Outer2.Inner2 inObj2 = new Outer2().new Inner2();
inObj2.method1();
System.out.println();
// 內、外部程式都只使用一次
new Outer2().new Inner2().method1();
}
}

▼ 記憶體空間配置

編譯錯誤 1:
xxxxxxxxxx
class Outer1{
class Inner1{
static int x=100;
}
}
class TestInner1{
public static void main(String[] arg){
}
}
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 在配置靜態的東西時,會與非靜態的記憶體區塊不同,所以會無法找到此非靜態區域變數。
使用靜態內部類別的語法如下:
執行成功 2:
xxxxxxxxxx
class Outer3 {
private int a = 10;
private static int sx = 200;
public void method1(){
System.out.println("外部method1()");
}
static class Inner3{
static int x = 100;
int a = 20;
public void method1(){
int a = 30;
System.out.println("內部 method1()");
System.out.println("區域變數 a : " + a);
System.out.println("內部屬性變數 a : " + this.a);
//變數名稱沒有重複可以直接使用sx。
System.out.println("全域變數 sx : " + Outer3.sx);
//變數名稱沒有重複可以直接使用x。
System.out.println("內部靜態屬性變數 x : " + Outer3.Inner3.x);
}
static public void method2(){
int a = 40;
System.out.println("內部靜態 method2()");
System.out.println("區域變數 a : " + a);
//變數名稱沒有重複可以直接使用sx。
System.out.println("全域變數 sx : " + Outer3.sx);
//變數名稱沒有重複可以直接使用x。
System.out.println("內部靜態屬性變數 x : " + Outer3.Inner3.x);
}
}
}
class TestInner3 {
public static void main(String[] arg){
// 使用非靜態方法時,必須建立物件實例才可以使用
Outer3.Inner3 inObj = new Outer3.Inner3();
inObj.method1();
System.out.println();
// 匿名物件
new Outer3.Inner3().method1();
System.out.println();
// 宣告為靜態全部程式都可以使用,只要存取權限不使用 private。
System.out.println("內部靜態屬性變數 x : " + Outer3.Inner3.x);
System.out.println();
Outer3.Inner3.method2();
}
}

編譯錯誤 2:
靜態內部類別有一個非靜態屬性變數 a,當 JVM 配置記憶體時,靜態內部類別會先建立起來,包含靜態屬性變數與方法,但非靜態屬性變數 a 並不會建立,所以 method2() 的記憶體內找不到非靜態屬性變數 a。
xxxxxxxxxx
class Outer4 {
static class Inner4 {
int a = 20;
static public void method2(){
System.out.println("內部屬性變數 a : " + this.a);
}
}
}
class TestInner4 {
public static void main(String[] arg){
Outer4.Inner4.method2();
}
}
TestInner4.java:5: error: non-static variable this cannot be referenced from a static context System.out.println("內部屬性變數 a : " + this.a); ^ 1 error
編譯錯誤 3:
因為靜態內部類別沒有建立外部物件,所以無法取得外部物件實例。
xxxxxxxxxx
class Outer5 {
private int a = 10;
static class Inner5 {
public void method1(){
System.out.println("外部屬性變數 a : " + Outer5.this.a);
}
}
}
class TestInner5 {
public static void main(String[] arg){
new Outer5.Inner5().method1();
}
}
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 內部類別 {
...
}
...
內部類別 物件參考變數 = new 內部類別建構子();
}
...
}
執行成功 3:
xxxxxxxxxx
class Outer6{
public void method1(){
int a = 20;
System.out.println("外部 method1()");
System.out.println("宣告內部類別 Inner6");
class Inner6{
public void method1(){
System.out.println("內部 method1()");
System.out.println("a = " + a);
}
}
System.out.println("建立 Inner6 類別物件");
Inner6 inObj = new Inner6();
inObj.method1();
}
}
class TestInner6{
public static void main(String[] arg){
new Outer6().method1();
}
}

編譯錯誤 4:
xxxxxxxxxx
class Outer7{
public void method1(){
int a = 20;
class Inner7{
public void method1(){
System.out.println("內部 method1()");
System.out.println("a = " + (a++));
}
}
Inner7 inObj = new Inner7();
inObj.method1();
}
}
class TestInner7{
public static void main(String[] arg){
new Outer7().method1();
}
}
方法內的內部類別如果要使用區域變數時,必須宣告成 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 類別名稱)
物件參考變數 保留
建構子() → 後面加上子類別{}全部內容
分號; 保留
語法就變成如下所示:
...
(實作介面中的所有抽象方法;)
...
};
此概念是在介面和抽象類別應用上發展起來的,因為這個類別是沒有名字的,所以也只能建構一次,如果經常會有臨時繼承某個類別或實作某個介面並建立實例的需求時,可以使用。
執行成功 4:匿名內部類別的物件

xxxxxxxxxx
class Circle{
double r;
public void setRadius(double r){
this.r=r;
}
public double area(){
return Math.PI*r*r;
}
}
class TestAnonInner1{
public static void main(String[] arg){
Circle basketBall = new Circle(){
public double area(){
return 4.0/3.0*super.area();
}
};
basketBall.setRadius(19.8);
System.out.println("球半徑:" + basketBall.r);
System.out.println("球體積:" + basketBall.area());
}
}

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

xxxxxxxxxx
interface Round{
Round setRadius(double r);
double area();
void showResult();
}
class TestAnonInner2{
public static void main(String[] arg){
//匿名類別的物件
System.out.println("匿名類別的物件:");
Round rd = new Round(){
double r;
public Round setRadius(double r){
this.r = r;
return this;
}
public double area(){
return Math.PI*r*r;
}
public void showResult(){
System.out.println("圓半徑:"+r);
System.out.println("圓面積:"+area());
}
};
rd.setRadius(19.8).showResult();
//匿名類別的匿名物件.
System.out.println("匿名類別的匿名物件:");
new Round(){
double r;
public Round setRadius(double r){
this.r = r;
return this;
}
public double area(){
return Math.PI*r*r;
}
public void showResult(){
System.out.println("圓半徑:"+r);
System.out.println("圓面積:"+area());
}
}.setRadius(10).showResult();
}
}

六、總結
如果一個類別只服務一個類別時,我們可以把它變成內部類別(Inner class)。
非靜態內部類別:
(i) 成員內部類別(Member inner class),也稱為內部類別。
(ii) 區域內部類別(Local inner class),也稱為方法內部類別。
(iii) 匿名內部類別(Anonymous inner class),也稱為匿名類別。
靜態內部類別:
(i) 靜態成員內部類別(Static Member inner class),也稱為靜態內部類別。
有個疑惑是,為何靜態內部類別無法像非靜態內部類別一樣產生物件呢?
回覆刪除以你的例子來說就是
outClass.Inner3 inObj = new Outer3().new Inner3();
為何會編譯錯誤呢?