類與介面(二)java的四種內部類詳解

程式設計師歐陽思海發表於2019-03-03

引言

內部類,巢狀在另一個類的裡面,所以也稱為 巢狀類;

內部類分為以下四種:

  • 靜態內部類
  • 成員內部類
  • 區域性內部類
  • 匿名內部類

一、靜態內部類

靜態內部類: 一般也稱”靜態巢狀類“,在類中用static宣告的內部類。

因為是static,所以不依賴於外圍類物件例項而獨立存在,靜態內部類的可以訪問外圍類中的所有靜態成員,包括private的靜態成員

同時靜態內部類可以說是所有內部類中獨立性最高的內部類,其建立物件、繼承(實現介面)、擴充套件子類等使用方式與外圍類並沒有多大的區別。

public class OuterClass {//外圍類
   
    public int aa; //例項成員
    private static float f = 1.5f;//private的靜態成員
    
    static void println() {
        System.out.println("這是靜態方法");
    }
    
    protected static class StaticInnerClass{//protected的靜態內部類
        
        float a;
        
        public StaticInnerClass() {
             a = f;// 外圍類的private靜態變數
             println();//外圍類的靜態方法
        }
    }
}

class OtherClass{

   public static void main(String[] args) {
       //建立靜態內部類的物件
    OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass(); 
   } 
}
複製程式碼

二、成員內部類

成員內部類: 定義在類的內部,而且與成員方法、成員變數同級,即也是外圍類的成員之一,因此 成員內部類 與 外圍類 是緊密關聯的。

注意:
這種緊密關聯指的是,成員內部類的物件的建立必須依賴於外圍類的物件(即沒有外圍類物件,就不可能建立成員內部類)。因此,成員內部類有以下3個特點:

  • 成員內部類可以訪問外圍類的所有成員,包括私有成員;
  • 成員內部類是不可以宣告靜態成員(包括靜態變數、靜態方法、靜態成員類、巢狀介面),但有個例外—可以宣告 static final的變數, 這是因為編譯器對final型別的特殊處理,是直接將值寫入位元組碼;
  • 成員內部類物件都隱式地儲存了一個引用,指向建立它的外部類物件;或者說,成員內部類的入口是由外圍類的物件保持著(靜態內部類的入口,則直接由外圍類保持著)

成員內部類中的 this,new關鍵字:

  • 獲取外部類物件:OuterClass.this
  • 明確指定使用外部類的成員(當內部類與外部類的名字衝突時):
    OuterClass.this.成員名
  • 建立內部類物件的new:外圍類物件.new
//先建立外圍類物件
OuterClass outer=new OuterClass();
//建立成員內部類物件
OuterClass.InnerClass inner=outer.new InnerClass();
複製程式碼

1. 成員內部類的物件建立

我們知道,成員內部類就像外圍類的例項成員一樣,一定要存在物件才能訪問,即成員內部類必須繫結一個外圍類的物件。上面已經介紹了成員內部類的建立格式了,我們直接看一個例子

public class OuterClass {//外圍類
   
    public int aa; //例項成員
    private static float f = 1.5f;//private的靜態成員
    
    public void initInnerClass() {
        System.out.println("內部類的初始化方法");
    }
    
    public void createInnerClass() {//
        //外圍類的成員方法中建立成員內部類物件
        InnerClass innerClass = new InnerClass();
    }
    
    class InnerClass{//成員內部類
        private double aa; //與圍類的變數aa的名字重複
        
        public InnerClass(){
            this.aa = OuterClass.this.aa + f;//明確指定兩個aa的所屬
            initInnerClass();
        }
    }   
}

//其他類
class OtherClass{
    public static void main(String[] args) {
        //其他類中建立成員內部類
        OuterClass oc = new OuterClass();//外部類物件
        //建立內部類物件
        OuterClass.InnerClass innerClass = oc.new InnerClass();
    }
}
複製程式碼

注意上面的例子中,在外圍類的成員方法中建立成員內部類 與 在其他類中或靜態方法中建立成員內部的方式是不一樣的。

補充幾點:

  • 成員內部類可以繼續包含成員內部類,而且不管一個內部類被巢狀了多少層,它都能透明地訪問它的所有外部類所有成員;
  • **成員內部可以繼續巢狀多層的成員內部類,但無法巢狀靜態內部類;**靜態內部類則都可以繼續巢狀這兩種內部類。

下面的例子是基於上面的例子進行改造:

class InnerClass{//成員內部類
        private double aa; //與圍類的變數aa的名字重複
        
        public InnerClass(){
            this.aa = OuterClass.this.aa + f;//明確指定兩個aa的所屬
            initInnerClass();
        }
        
        public  class InnerInnerCalss2{//成員內部類中的成員內部類
            protected double aa = OuterClass.this.aa;//最外層的外圍類的成員變數
        }//InnerInnerCalss2
    }//InnerClass
複製程式碼

2. 繼承成員內部類

在內部類的訪問許可權允許的情況下,成員內部類也是可以被繼承的。由於成員內部類的物件依賴於外圍類的物件,或者說,成員內部類的構造器入口由外圍類的物件把持著。因此,繼承了成員內部類的子類必須要與一個外圍類物件關聯起來。同時,子類的構造器是必須要呼叫父類的構造器方法,所以也只能通過父類的外圍類物件來呼叫父類構造器。

下面的例子也是基於上面的例子的,只貼出多出的部分程式碼。

class ChildClass extends OuterClass.InnerClass{
    
    //成員內部類的子類的構造器的格式
    public ChildClass(OuterClass outerClass) {
        outerClass.super();//通過外圍類的物件呼叫父類的構造方法
    }
}
複製程式碼

三、區域性內部類

區域性內部類: 就是在方法、構造器、初始化塊中宣告的類,在結構上類似於一個區域性變數。因此區域性內部類是不能使用訪問修飾符

區域性內部類的兩個訪問限制

  • 對於區域性變數,區域性內部類只能訪問final的區域性變數。不過,後期JDK(忘了是JDK幾了)區域性變數可不用final修飾,也可以被區域性內部類訪問,但你必須時刻記住此區域性變數已經是final了,不能再改變。
  • 對於類的全域性成員,區域性內部類定義在例項環境中(構造器、物件成員方法、例項初始化塊),則可以訪問外圍類的所有成員;但如果內部類定義在靜態環境中(靜態初始化塊、靜態方法),則只能訪問外圍類的靜態成員。
public class OuterClass {
    
    private int a = 21;
    
    static {//靜態域中的區域性內部類
       class LocalClass1{
              //  int z = a; //錯誤,在靜態的作用域中無法訪問物件成員
       }
    }
    
    {//例項初始化塊中的區域性內部類
        class localClass2{          
        }
    }
    
    public OuterClass(){
        int x = 2;
        final int y = 3;
        // x = 3;//若放開此行註釋,編譯無法通過,因為區域性變數x已經是final型別
        //構造器中的區域性內部類
        class localClass3{
            int z = y; //可以訪問final的區域性變數
            int b = a;//可以訪問類的所有成員
             //訪問沒有用final修飾的區域性變數
            int c = x;
        }
    }
    
    public void createRunnable() {
        
        final int x = 4;
        //方法中的區域性內部類
        class LocalClass4 implements Runnable {//
            @Override
            public void run() {
                System.out.println("區域性final變數:"+x);
                System.out.println("物件成員變數:"+a);
            }
            
        }
    }
}
複製程式碼

四、匿名內部類

匿名內部類: 與區域性內部類很相似,只不過匿名內部類是一個沒有給定名字的內部類,在建立這個匿名內部類後,便會立即用來建立並返回此內部類的一個物件引用。

作用:匿名內部類用於隱式繼承某個類(重寫裡面的方法或實現抽象方法)或者實現某個介面。

匿名內部類的訪問限制: 與區域性內部類一樣,請參考區域性內部類;

匿名內部類的優缺點:
優點: 編碼方便快捷;
缺點:

  • 只能繼承一個類或實現一個介面,不能再繼承其他類或其他介面。
  • 只能用於建立一次物件例項;

下面的例子是我們建立執行緒時經常用到的匿名內部類的方式來快速地建立一個物件的例子:

 class MyOuterClass {

    private int x = 5;

    void createThread() {
        
        final int a = 10;
        int b = 189;
        
        // 匿名內部類繼承Thread類,並重寫Run方法
        Thread thread = new Thread("thread-1") {

            int c = x;  //訪問成員變數
            int d = a;  //final的區域性變數
            int e = b; //訪問沒有用final修飾的區域性變數
            
            @Override
            public void run() {
                System.out.println("這是執行緒thread-1");
            }
        };

        // 匿名內部類實現Runnable介面
        Runnable r = new Runnable() {

            @Override
            public void run() {
                System.out.println("執行緒執行中");
            }
        };
    }
}
複製程式碼

總結

類 型 訪問修飾符 宣告靜態成員 繫結外圍類
靜態內部類 四種訪問修飾符 -可以宣告 不繫結
成員內部類 四種訪問修飾符 除 final static 的變數外,其餘靜態成員都不行 繫結
區域性內部類 不可以宣告 不可以宣告 取決於此內部類的宣告環境
匿名內部類 不可以宣告 不可以宣告 取決於此內部類的宣告環境

出處:http://www.cnblogs.com/jinggod/p/8503143.html
文章有不當之處,歡迎指正,你也可以關注我的微信公眾號:好好學java,獲取優質學習資源。

相關文章