1. 程式人生 > >內部類詳解

內部類詳解

我們知道內部類可分為以下幾種:

  • 成員內部類
  • 靜態內部類
  • 方法內部類
  • 匿名內部類

這裡我們先將以這個分類來詳細瞭解各個內部類的情況。然後給內部類作出總結。

一、成員內部類

  內部類中最常見的就是成員內部類,也稱為普通內部類。我們來看如下程式碼:

  

  執行結果為:

  從上面的程式碼中我們可以看到,成員內部類的使用方法

  1、 Inner 類定義在 Outer 類的內部相當於 Outer 類的一個成員變數的位置,Inner 類可以使用任意訪問控制符,如 public 、 protected 、 private 等

  2、 Inner 類中定義的 test() 方法可以直接訪問

 Outer 類中的資料,而不受訪問控制符的影響,如直接訪問 Outer 類中的私有屬性a

  3、 定義了成員內部類後,必須使用外部類物件來建立內部類物件,而不能直接去 new 一個內部類物件,即:內部類 物件名 = 外部類物件.new 內部類( );

  4、 編譯上面的程式後,會發現產生了兩個 .class 檔案

  

  其中,第二個是外部類的 .class 檔案,第一個是內部類的 .class 檔案,即成員內部類的 .class 檔案總是這樣:外部類名$內部類名.class

另外,友情提示哦:

  1、 外部類是不能直接使用內部類的成員和方法滴。如:

  

  那麼外部類如何使用內部類的成員和方法呢??

  答:可先建立內部類的物件,然後通過內部類的物件來訪問其成員變數和方法。

  Java 編譯器在建立內部類物件時,隱式的把其外部類物件的引用也傳了進去並一直儲存著。這樣就使得內部類物件始終可以訪問其外部類物件,同時這也是為什麼在外部 類作用範圍之外向要建立內部類物件必須先建立其外部類物件的原因。

 理解:(有個類Outer,裡面有個內部類Inner。所以上面可以理解的就是Inner裡面獲得了Outer.this這個外部類的引用,而對於為什麼在外部 類作用範圍之外向要建立內部類物件必須先建立其外部類物件的原因這句話的意思就是建立了Outer類這個物件之後,Outer.this才能指向真正的物件)

  2、 如果外部類和內部類具有相同的成員變數或方法,內部類預設訪問自己的成員變數或方法,如果要訪問外部類的成員變數,可以使用 this 關鍵字。如:

  

  執行結果:

二、靜態內部類

  靜態內部類是 static 修飾的內部類,這種內部類的特點是:

  1、 靜態內部類不能直接訪問外部類的非靜態成員,但可以通過 new 外部類().成員 的方式訪問。

  2、 如果外部類的靜態成員與內部類的成員名稱相同,可通過“類名.靜態成員”訪問外部類的靜態成員;如果外部類的靜態成員與內部類的成員名稱不相同,則可通過“成員名”直接呼叫外部類的靜態成員。

  3、 建立靜態內部類的物件時,不需要外部類的物件,可以直接建立 內部類 物件名= new 內部類();

   

  執行結果 : 

三、方法內部類

  方法內部類就是內部類定義在外部類的方法中,方法內部類只在該方法的內部可見,即只在該方法內可以使用

   

  一定要注意哦:由於方法內部類不能在外部類的方法以外的地方使用,因此方法內部類不能使用訪問控制符和 static 修飾符。

 四、匿名內部類

  匿名類是不能有名稱的類,所以沒辦法引用他們。必須在建立時,作為new語句的一部分來宣告他們。

  但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個介面。
  這就要採用另一種形式 的new語句,如下所示: 

   new <類或介面> <類的主體>

        這種形式的new語句宣告一個 新的匿名類,他對一個給定的類進行擴充套件,或實現一個給定的介面。他還建立那個類的一個新例項,並把他作為語句的結果而返回。要擴充套件的類和要實現的介面是 new語句的運算元,後跟匿名類的主體。 

        注意匿名類的宣告是在編譯時進行的,例項化在執行時進行。這意味著 for迴圈中的一個new語句會建立相同匿名類的幾個例項,而不是建立幾個不同匿名類的一個例項。

        從技術上說,匿名類可被視為非靜態的內 部類,所以他們具備和方法內部宣告的非靜態內部類相同的許可權和限制。

        假如要執行的任務需要一個物件,但卻不值得建立全新的物件(原因可能 是所需的類過於簡單,或是由於他只在一個方法內部使用),匿名類就顯得很有用。匿名類尤其適合在Swing應用程式中快速建立事件處理程式。以下是一個匿名內部類的例項:

  1、匿名內部類的基本實現:

    

  執行結果: 

  可以看到,我們直接將抽象類Person中的方法在大括號中實現了,這樣便可以省略一個類的書寫,並且,匿名內部類還能用於介面上。

  2、在介面上使用匿名內部類:

  

  執行結果:

  由上面的例子可以看出,只要一個類是抽象的或是一個介面,那麼其子類中的方法都可以使用匿名內部類來實現。

  在使用匿名內部類的過程中,我們需要注意如下幾點:

      1、使用匿名內部類時,我們必須是繼承一個類或者實現一個介面,但是兩者不可兼得,同時也只能繼承一個類或者實現一個介面。

      2、匿名內部類中是不能定義建構函式的。

      3、匿名內部類中不能存在任何的靜態成員變數和靜態方法。

      4、匿名內部類為區域性內部類(即方法內部類),所以區域性內部類的所有限制同樣對匿名內部類生效。

      5、匿名內部類不能是抽象的,它必須要實現繼承的類或者實現的介面的所有抽象方法。

匿名內部類重點難點

1. 如果是在一個方法的匿名內部類,可以利用這個方法傳進你想要的引數,不過記住,這些引數必須被宣告為 final 。

 使用的形參為何要為final??

 我們給匿名內部類傳遞引數的時候,若該形參在內部類中需要被使用,那麼該形參必須要為final。也就是說:當所在的方法的形參需要被內部類裡面使用時,該形參必須為final。

     首先我們知道在內部類編譯成功後,它會產生一個class檔案,該class檔案與外部類並不是同一class檔案,僅僅只保留對外部類的引用。當外部類傳入的引數需要被內部類呼叫時,從java程式的角度來看是直接被呼叫:

複製程式碼

    public class OuterClass {  
        public void display(final String name,String age){  
            class InnerClass{  
                void display(){  
                    System.out.println(name);  
                }  
            }  
        }  
    }  

複製程式碼

  從上面程式碼中看好像name引數應該是被內部類直接呼叫?其實不然,在java編譯之後實際的操作如下:

複製程式碼

    public class OuterClass$InnerClass {  
        public InnerClass(String name,String age){  
            this.InnerClass$name = name;  
            this.InnerClass$age = age;  
        }  
          
          
        public void display(){  
            System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );  
        }  
    }  

複製程式碼

 所以從上面程式碼來看,內部類並不是直接呼叫方法傳遞的引數,而是利用自身的構造器對傳入的引數進行備份,自己內部方法呼叫的實際上時自己的屬性而不是外部方法傳遞進來的引數。

 直到這裡還沒有解釋為什麼是final。在內部類中的屬性和外部方法的引數兩者從外表上看是同一個東西,但實際上卻不是,所以他們兩者是可以任意變化的,也就是說在內部類中我對屬性的改變並不會影響到外部的形參,而然這從程式設計師的角度來看這是不可行的,畢竟站在程式的角度來看這兩個根本就是同一個,如果內部類該變了,而外部方法的形參卻沒有改變這是難以理解和不可接受的,所以為了保持引數的一致性,就規定使用final來避免形參的不改變。對於內部類引用的引數當你不寫final,其會預設為final。

   簡單理解就是,拷貝引用,為了避免引用值發生改變,例如被外部類的方法修改等,而導致內部類得到的值不一致,於是用final來讓該引用不可改變。

   故如果定義了一個匿名內部類,並且希望它使用一個其外部定義的引數,那麼編譯器會要求該引數引用是final的。

2. 匿名內部類中使用初始化程式碼塊

   我們一般都是利用構造器來完成某個例項的初始化工作的,但是匿名內部類是沒有構造器的!那怎麼來初始化匿名內部類呢?使用構造程式碼塊!利用構造程式碼塊能夠達到為匿名內部類建立一個構造器的效果。

複製程式碼

    public class OutClass {  
        public InnerClass getInnerClass(final int age,final String name){  
            return new InnerClass() {  
                int age_ ;  
                String name_;  
                //構造程式碼塊完成初始化工作  
                {  
                    if(0 < age && age < 200){  
                        age_ = age;  
                        name_ = name;  
                    }  
                }  
                public String getName() {  
                    return name_;  
                }  
                  
                public int getAge() {  
                    return age_;  
                }  
            };  
        }  
          
        public static void main(String[] args) {  
            OutClass out = new OutClass();  
              
            InnerClass inner_1 = out.getInnerClass(201, "chenssy");  
            System.out.println(inner_1.getName());  
              
            InnerClass inner_2 = out.getInnerClass(23, "chenssy");  
            System.out.println(inner_2.getName());  
        }  
    }  

複製程式碼