天天看點

Java 内部類詳解

什麼

定義在一個類内部的類,稱為内部類(累不累),如下:

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

C稱為A的内部類,簡稱内部類

A稱為C的外部類,簡稱外部類

而且内部類能通路外部類的成員(靜态成員、執行個體成員),當然有一些限制,限制如下

4種聲明方式

按照内部類的聲明方式,分為4種内部類:

  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. 成員内部類

    新類的執行個體成員(未加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);
		}
	}
}
           
Java 内部類詳解
  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來看看:

Java 内部類詳解

秘密就來自,編譯器為外部類生的兩個靜态通路方法,

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:

Java 内部類詳解
  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. 使代碼更簡潔,匿名内部類