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聲明

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM