Java 內部類詳解

什麼

定義在一個類內部的類,稱爲內部類(累不累),以下:java

public class A {
    private int c = 1;
        
    public class C {
        public void test() {
            System.out.println("c:" + c);
        }
    }
}

C稱爲A的內部類,簡稱內部類this

A稱爲C的外部類,簡稱外部類code

並且內部類能訪問外部類的成員(靜態成員、實例成員),固然有一些限制,限制以下對象

4種聲明方式

按照內部類的聲明方式,分爲4種內部類:blog

  1. 靜態內部類繼承

    像類的靜態成員同樣聲明的類,就稱呼爲「靜態內部類」接口

    public class A {
         private static String b = "b";
             private int c = 1;
    
             // B是A的靜態內部類
         public static class B {
             public void test() {
                 System.out.println(b);
             }
         }
     }

    靜態內部類,只能訪問外部類的靜態成員(方法和變量),而且能夠像類的成員同樣使用修飾符(public/protected/private);編譯器

    建立靜態內部類對象的方式:A.B b = new A.B();編譯

  2. 成員內部類class

    新類的實例成員(未加static修飾)聲明的類,稱爲「成員內部類」

    public class A {
         private static String b = "b";
             private int c = 1;
    
             // C是A的成員內部類
         public class C {
             public void test() {
                 System.out.println(c);
                             System.out.println(b);
             }
         }
     }

    成員內部類,訪問外部類的一切(靜態,仍是實例),就像成員方法同樣,而且能夠像類的成員同樣使用修飾符(public/protected/private)

    建立成員內部類對象的方式:

    A a = new A();
     A.C c = a.new C();
  3. 方法內部類

    在一個代碼塊聲明的類稱爲方法內部類,代碼塊包括(方法內、靜態代碼塊內、實例代碼塊內)

    public class A {
         private static String b;
         private int c;
    
             // 成員方法
         public void test() {
             final int d = 1;
             // 方法內部類
             class D {
                 public void test() {
                     // 訪問靜態變量
                     System.out.println(b);
                     // 訪問實例變量
                     System.out.println(c);
                     // 訪問方法final類型的局部變量
                     System.out.println(d);
                 }
             }
         }
     }

    方法內部類,和它所在的方法(代碼塊),具備相同的訪問能力,若是上面代碼是在static方法中聲明的,那麼內部類D不能訪問c變量。

    jdk1.8 方法內部類,可以訪問非final類型的局部變量,本質至關有在內部類D內保存了副本

  4. 匿名內部類

    匿名內部類也就是沒有名字的內部類

    正由於沒有名字,因此匿名內部類只能使用一次,它一般用來簡化代碼編寫

    但使用匿名內部類還有個前提條件:必須繼承一個父類或實現一個接口

內部類的本質

內部類的語法頗爲奇怪,咱們來看看以下代碼,編譯後的字節碼文件!

public class A {
    private static String b = "b";
    private int c = 1;
        // 靜態內部類
    public static class B {
        public void b() {
            System.out.println(b);
        }
    }
        // 成員內部類
    class C {
        public void c() {
            System.out.println(c);
        }
    }
}

字節碼文件

  1. A.java文件被編譯成了多個class文件
  2. A類對應A.class
  3. B類對應A$B.class
  4. C類對應A$C.class

內部類會被編譯成單獨的class文件,那意味JVM解釋執行class文件時類「B」和類A是獨立的,由此能夠見內部類也是一種語法糖!

對於JVM來講,類A的private b和c 成員,怎麼能分別被類B和類C訪問到的了!

用javap命令反編譯類A.class來看看:

祕密就來自,編譯器爲外部類生的兩個靜態訪問方法,Stinrg access$000()返回b變量的值,int access$100(A a)返回a對象的c成員變量值;

而在靜態內部類B中,編譯器將訪問靜態變量b的地方替換爲如上方法:

// 靜態內部類
public static class A$B {
    public void b() {
        System.out.println(A.access$000());
    }
}

在成員內部類C中,原理也是如此,不過增長了更多的東西,反編譯A$C.class:

  1. 新增了成員字段final A $this;
  2. 構造方法添加形參 `A$C(A obj);
  3. 訪問外部類成員變量的地方會被替換成:System.out.println(A.access$100($this));

你必定會好奇成員構造方法中的外部類對象的參數從哪裏傳入的!看看咱們是怎麼聲明內部類的對象的

A a = new A();
A.C c = a.new C();

將會被編譯器替換成:

A a = new A();
A$C c = new A$C(a);

內部類的使用時機

兩個類之間緊密聯繫時,可使用內部類:

  1. 當一個類須要訪問另一個類的許多屬性時,內部類能夠簡化訪問代碼
  2. 實現更好封裝性,好比:B 類僅僅被A類訪問時,能夠將B類做爲A的私有內部類
  3. 使代碼更簡潔,匿名內部類
相關文章
相關標籤/搜索