1. 程式人生 > >java內部類深入詳解 內部類的分類 特點 定義方式 使用

java內部類深入詳解 內部類的分類 特點 定義方式 使用

 本文關鍵詞:

java內部類 內部類的分類 特點  定義方式 使用   外部類呼叫內部類 多層巢狀內部類  內部類訪問外部類屬性  介面中的內部類  內部類的繼承  內部類的覆蓋  區域性內部類 成員內部類 靜態內部類 匿名內部類

內部類定義

將一個類定義放到另一個類的內部,這就是內部類

內部類與組合是完全不同的概念

內部類指的是類的定義在內部

看起來像一種程式碼隱藏機制

但是,遠不止於此,因為他了解外部類 並且能夠通訊

內部類的程式碼,可以操作建立它的外部類的物件

所以可以認為內部類提供了某種進入其外部類的視窗

內部類特點

內部類訪問外部類不需要任何特殊條件,擁有外部類所有的訪問權

也就是對於內部類訪問外部類的元素這件事情上

他就相當於是外部類本身一樣隨便訪問

內部類的建立依賴外部類物件

可以直接訪問外部類的變數

也可以直接指明

外部類類名.this.變數名

this通常是多餘的,可以省略

內部類不僅能夠訪問包含他的外部類,還可以訪問區域性變數

但是區域性變數必須被宣告為final

因為區域性內部類會將呼叫的變數進行拷貝,為了保證一致性,所以變數必須為final

內部類就是隱匿在外部類內部的一個獨立的個體,不存在is a  like a

內部類的物件必定祕密的捕獲了一個指向外部類物件的引用

然後以此訪問外部類的成員,編譯器處理了所有的細節,對我們來說都是透明的

public class O {

    
    class I{
        
        O get() {
            return O.this;
        }
    }
    
    
    public static void main(String[] args) {

        O outer = new O();
        O.I inner = outer.new I();
        System.out.println(outer == inner.get());

    }

}

列印結果為:

true

內部類持有的外部類物件就是外部類物件本身,記憶體地址是相同的

外部類的作用域之外,可以使用  outerClass.innerClass  方式引用內部類

可以對同一個包中其他類隱藏

內部類可以宣告為私有的

每個類都會產生一個.class檔案,包含了類的元資訊

如果內部類是匿名的,編譯器會簡單的產生一個數字作為識別符號形如 Outer$1.class

否則就是形如  外部類$內部類.class   ,虛擬機器看來與其他類無差,這也是編譯器做的工作

普通的類(外部類)只能用public修飾符修飾,或者不寫修飾符 使用預設的,但是內部類可以使用private 與protected

內部類可以達到類似"多重繼承"的效果,

每個內部類都能獨立的繼承自一個(介面的)實現

無論外部類是否已經繼承了某個(介面的)實現

也就是說 單個外部類,可以讓多個內部類以不同的方式實現同一個介面或者繼承同一個類

一個外部類可以建立多個內部類,這是不是就達到了類似"多重繼承"的效果呢

內部類分類

  1. 成員內部類
  2. 區域性內部類
  3. 匿名內部類
  4. 靜態內部類

成員內部類

成員內部類也叫例項內部類。每一個外部類物件都需要一個內部類的例項,內部類離不開外部類存在

既然是成員內部類,和成員屬性成員方法地位上自然沒有什麼不同

每個外部類物件都有一個內部類物件,自然持有外部類的引用

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//注意是物件.new

區域性內部類

區域性內部類不能用public或者private或者protected訪問說明符,作用域被限定在了宣告這個區域性內部類中了

很好理解,區域性的就跟方法變數一樣,限定在了{}之中,自然就不需要設定訪問說明符了,而且你可以想下,也只有類以及類的成員有訪問修飾符,區域性變數有訪問修飾符麼

區域性類可以對外面完全的隱藏起來,即使是外部類的其他的程式碼也不能訪問他

區域性內部類雖然被限定在區域性程式碼塊{} 裡面,但是他也是可以訪問外部類的屬性的,不要被分類迷惑了

匿名內部類

匿名內部類就是區域性內部類的進一步隱藏,區域性內部類定義了之後在區域性區域內仍舊可以建立多個物件

匿名內部類宣告一個類之後就只能建立一個物件了,因為他並沒有類名字

形式為:

new xxxClass  (){    //或者new xxxInterface()
//.......

}

表示建立一個類的物件,這個類是xxxClass  子類或者實現了xxxInterface 介面的類

也可以說匿名內部類就是建立了一個匿名類的子類物件

構造方法名字和類名是相同的,匿名內部類顯然是沒有構造方法的,因為連名字都沒有

既然沒有構造方法想要構造引數,就只能把引數傳遞給外部的構造器,通過外部類的構造器繞一圈,本身內部類可以訪問外部類所有的屬性,去把值操作起來

當然外部類自然可以搞點屬性根據業務邏輯單獨給內部類用

如果是實現介面,不能帶任何的引數的,因為介面都沒有構造方法的呀

 不過還可以通過初始化程式碼塊達到類似的初始化效果,想必大家還記得初始化程式碼塊是什麼吧

不過也僅僅是達到類似的效果,而且,相當於只有一個"構造方法",因為即使你寫了多個初始化程式碼塊,還不是構造物件的時候一起執行嘛

小技巧,匿名內部類的引數傳遞

 fun(new ArrayList<String>(){{add("a");add("b");add("c");}});

也就是:
fun(new ArrayList<String>(){    

               {

                 add("a");

                 add("b");

                 add("c");

               }

          }

);
  1. 構造了一個匿名內部類,內部類沒有更新重寫增加任何的方法
  2. 設定了一個初始化塊  {}  ,初始化塊會在每個物件構造的時候執行
  3. 程式碼塊中呼叫add方法增加物件

靜態內部類

如果使用內部類只是為了將一個類隱藏到一個類的內部

並不需要內部類引用外部類的物件

可以將內部類宣告為static,以便取消產生的引用

只有內部類可以宣告為static

靜態內部類的物件除了沒有對生成他的外部類的物件的引用特權外,其他的內部類一樣

通過  外部類 . 內部類   來訪問

剛才已經說了顯然,靜態內部類不會持有外部類的引用

靜態的建立形式:

Outer.Inner inner = new Outer.Inner();

內部類的繼承

內部類的構造器必須連線到指向外部類物件的引用

但是在繼承的時候

那個指向外部類物件的"隱匿的"引用必須被初始化

而在派生類中不再存在可連線的預設物件

所以你要解決這個問題,否則的話就會出錯

說的就是要包含指向外部類的引用

必須是帶引數的,而且引數型別是外部類 在這裡面呼叫super

public class InnerInherit extends OutClass.Inner {

    InnerInherit(OutClass out){
        out.super();
    }

    public static void main(String[] args){
        OutClass out = new OutClass();
        InnerInherit ii = new InnerInherit(out);
    }
}


class OutClass {
    class Inner{
    }
}

可以看得到,雖然只是繼承內部類

但是想要生成一個構造器,不僅僅是需要傳遞一個外部類的引用

必須在構造器中使用:

enclosingClassReference.super();

說白了就是,內部類的物件依賴外部類的物件

內部類的子類的物件,也仍舊是依賴外部類的物件的

內部類的載入時機

package test.b;

public class Outer {

    Outer(){
         System.out.println("Outer構造方法");
     }
     
     {
         
         System.out.println("Outer初始化程式碼塊");
     }
     static{
         
         System.out.println("Outer靜態程式碼塊");
     }
     
     class Inner{
         
         Inner(){
             System.out.println("Inner構造方法");
         }
         
         {
             
             System.out.println("Inner初始化程式碼塊");
         }

        
     }
     
     public static void main(String[] args) {
         Outer outer = new Outer();
         System.out.println("----------");
         //Outer.Inner inner = outer.new Inner();

    }

}

列印結果:

Outer靜態程式碼塊
Outer初始化程式碼塊
Outer構造方法
----------

顯然,內部類沒有被初始化,放開註釋

列印結果:

Outer靜態程式碼塊
Outer初始化程式碼塊
Outer構造方法
----------
Inner初始化程式碼塊
Inner構造方法

所以可以說內部類是懶載入的 用到了才載入初始化

而且,可以建立多個內部類的例項

Outer.Inner inner1 = outer.new Inner();
Outer.Inner inner2 = outer.new Inner();
Outer.Inner inner3 = outer.new Inner();
Outer.Inner inner4 = outer.new Inner();

這是可以的,完全沒問題,每個例項有自己的狀態資訊,與外部類物件資訊獨立

內部類的覆蓋情況

兩個類之間的繼承和他們各自的內部類沒有關係,不存在覆蓋的情況

兩個類之間的繼承關係  比如  B extends A  ,每個類中都有C這個內部類

他們兩者中的C是沒有什麼關係的

示例:

類A  擁有內部類C 並且有一個C的物件,構造方法中初始化

類B繼承A,並且B中也有一個內部類C

public class A {

    private C c;
     A(){
         System.out.println("A  constructor");
         c = new C();
     }
     
     protected class C{
             C(){
                 System.out.println("A ....C  constructor");
             }
     }
     
     
     public static void main(String[] args) {

    }

}


public class B extends A{

    B(){
         System.out.println("B  constructor");

    }
     class C{
         C(){
             System.out.println("B ....C  constructor");
         }
}
     
     
     public static void main(String[] args) {

        new B();
     }

}

建立類B new B();

列印資訊:

A  constructor
A ....C  constructor
B  constructor

建立B的物件,需要呼叫父類的構造方法

所以會列印A  constructor  然後構造方法中建立C物件,然後是A ....C  constructor   顯然,這並不是B類中的C

所以說:

兩個類之間的繼承,不存在內部類被覆蓋的情況

雖然B繼承了A  A有C  B也有C

但是兩個內部類是完全獨立的兩個實體

各自在各自的名稱空間中

上面的例子中建立一個物件,有父類,呼叫父類的構造方法,父類的構造方法呼叫父類的C的構造方法,也找不到任何方法會要呼叫子類的C

主函式修改下:

public static void main(String[] args) {

    //new B();
     A a = new B();
     System.out.println("#############");
     
     B b = new B();
     System.out.println("#############");

    a.new C();
     System.out.println("#############");

    b.new C();
     System.out.println("#############");

}

列印結果為:

A  constructor
A ....C  constructor
B  constructor
#############
A  constructor
A ....C  constructor
B  constructor
#############
A ....C  constructor
#############
B ....C  constructor
#############

上面兩段很正常,都是建立B物件,自然步驟一樣

當建立a.new C(); 的時候使用的是A的C

當建立b.new C(); 的時候使用的是B的C

顯然,

建立內部類物件時,到底是父類中的還是子類中的 

是由:   .new 前面的型別決定的,也就是定義的型別,而不是實際指向的型別

多層巢狀的內部類

多層巢狀的內部類,他能透明的訪問所有他所嵌入的外圍類的所有成員

public class NestedClass {

    private String NestedClassName = "NestedClass";
     
     public class NestedClass1{
         private String NestedClass1Name = "NestedClass1";

        public class NestedClass2{
             private String NestedClass2Name = "NestedClass2";

            public class NestedClass3{
                 public void print() {
                     System.out.println("NestedClassName:   "+NestedClassName);
                     System.out.println("NestedClass1Name:   "+NestedClass1Name);
                     System.out.println("NestedClass1Name:   "+NestedClass2Name);
                 }
             }
         }
     }
     public static void main(String[] args) {
         NestedClass nestedClass = new NestedClass();
         NestedClass.NestedClass1 nestedClass1 = nestedClass.new NestedClass1();
         NestedClass.NestedClass1.NestedClass2 nestedClass2 = nestedClass1.new NestedClass2();
         NestedClass.NestedClass1.NestedClass2.NestedClass3 nestedClass3 = nestedClass2.new NestedClass3();
         nestedClass3.print();
         
         
     }

}

列印資訊

NestedClassName:   NestedClass
NestedClass1Name:   NestedClass1
NestedClass1Name:   NestedClass2

從程式碼中可以看的出來,多層內部類和一層內部類建立格式是一樣的

外部類名.內部類名 物件名 = 外部類物件.new 內部類名();

這個外部類指的就是他的外部,如果他的外部仍舊是別人的內部類,那就依次往外找就好了

從列印資訊可以看得出來,不管有幾層,內部類,可以訪問到他外面的所有的類的屬性資訊

介面中的內部類

一般情況下

介面中不允許放置任何程式碼,但是巢狀類可以作為介面的一部分

放到介面中的任何類都自動的是public 和 是 static 的

因為類是static,只是將巢狀類置於介面的名稱空間內,並不違反介面的規則

你甚至可以介面中的內部類實現外部介面

如果你想要建立某些公共程式碼,使得他們可以被某個介面的所有不同實現所共用

那麼使用介面內部的巢狀類會顯得很方便

示例:

public class Test {

    public static void main(String[] args) {
         // 介面中的內部類都是預設 public static 的
         Fly bird = new Fly.DemoFly();
         bird.fly();

        Fly bigBird = new BigBird();
         bigBird.fly();
     }

}

interface Fly {

    public void fly();

    class DemoFly implements Fly {

        @Override
         public void fly() {
             System.out.println("一般的鳥都這麼飛~");

        }

    }
}

class BigBird implements Fly {

    @Override
     public void fly() {
         System.out.println("大鳥都這麼飛~");
     }

}

列印資訊:

一般的鳥都這麼飛~
大鳥都這麼飛~

可以看得出來,直接通過內部類,介面的靜態內部類,可以提供一個預設的實現

這就是提供了程式設計介面的同時,又提供了一個預設的實現,多給力

內部類中不能有靜態屬性以及靜態方法以及靜態程式碼塊

class A{
    
    class B{
        private static int a= 0;//IDE會提示報錯的  
    }
}

非靜態的內部型別,不能宣告靜態的filed 除非標記為常量,也就是用final宣告