Java——內部類(概念理解+應用舉例)

1.開篇 

在 Java 這個面向對象的編程語言中,我們幾乎離不開“類”這個概念,在博主之前發佈的文章中,全部都是在某一個類中,定義一些成員屬性或者是成員方法,實例屬性(方法)也好、靜態屬性(方法)也好,最終都是可以通過規範的代碼形式去實現的。那麼,我們能否在一個類中創建另一個類呢?答案是:肯定的,這就是這篇博文要講的內容:內部類!!!

2.內部類的介紹 

在 Java 語言中,內部類是指在一個外部類的內部再定義一個類。而對於這個內部類來說,它可以是靜態 static 的,也可以用(訪問修飾符)public,protected,default 和 private 來修飾。(而包含這個內部類的外部類只能使用 public 和 default來進行修飾)。而在字節碼語言中,只有類的概念,沒有外部類和內部類的概念,類只能使用 public 和 default 進行訪問控制。 

注意:內部類是一個編譯器現象,JVM 虛擬機並不知道內部類與常規類有什麼不同。對於源文件中一個名爲 Outer 的外部類和其內部定義的名爲 Inner 的內部類,編譯成功後會出現 Outer.class 和 Outer$Inner.class 兩個字節碼文件以及文件中的 Outer 和 Outer$Inner 兩個類,編譯器會把內部類翻譯成用 $ (美元符號)分隔外部類名與內部類名的獨立的常規類名,而虛擬機則對此一無所知,虛擬機在運行的時候,會把 Outer$Inner 作爲一種常規類來處理的。所以內部類的成員變量/方法名可以和外部類的相同。

3.內部類的分類

上圖展示了在 Java 語言中,內部類的具體分類,下面,我主要爲大家介紹一下對這四種內部類的理解以及應用,即:成員內部類、靜態內部類、局部內部類和匿名內部類。

3.1 成員內部類 

3.1.1 概念理解 

在外部類的內部,定義的非靜態的內部類,叫成員內部類。 

① 外部類的所有實例成員對成員內部類可見。成員內部類的實例對象,有外部類的實例對象的引用,所以可以訪問外部類實例的所有成員(包括 private 私有的)。
② 外部類的靜態成員對成員內部類可見。成員內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
③ 外部類對成員內部類可見。在內部類中可以使用 new 生成外部類的實例對象。 

④ 成員內部類對外部類可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類中可以使用 new 生成內部類的實例對象。外部類按常規的類訪問方式使用內部類,唯一的差別是外部類可以訪問成員內部類的所有方法與屬性,包括私有方法與私有屬性。

⑤ 成員內部類可以使用 public、protected、default或private 訪問修飾符進行訪問控制,內部類能夠隱藏起來,不爲同一包的其它類訪問。成員內部類一般當做成員變量設置爲 private。
⑥ 成員內部類是非靜態的。所以在成員內部類中,不能定義靜態字段、靜態方法和靜態內部類,因爲成員內部類需要先創建外部類的實例對象,才能創建自己的對象;但是可以定義非靜態字段、非靜態方法和非靜態內部類。

3.1.2 成員內部類的特殊語法規則 

假設外部類爲 Cow,其成員內部類爲 CowLeg。

①在內部類實例對象的方法中引用外部類實例對象的私有數據域:Cow. this.weight 

②在外部類實例對象的方法中創建部類實例對象:CowLeg cl = this.new CowLeg(1.12, "黑白相間");

③如果內部類在外部類作用域之外是可見的,則可以這樣引用內部類:Cow.CowLeg

3.1.3 應用舉例 

public class Cow {
	private double weight;//外部類的私有成員變量
	public Cow(double weight) {//外部類的構造方法
		this.weight=weight;
	}
	private class CowLeg {//定義一個成員內部類
		private double length;//成員內部類的兩個實例變量
		private String color;
		//private static int age;由於成員內部類的實例的創建依賴於外部類的實例的創建,所以成員內部類不可以包含靜態成員
		public CowLeg(double length,String color) {//成員內部類的構造方法
			this.length=length;
			this.color=color;
		}
		//length、color的set和get方法
		public void setLength(double length) {
			this.length=length;
		}
		public double getLength() {
			return this.length;
		}
		public void setColor(String color) {
			this.color=color;
		}
		public String getColor() {
			return this.color;
		}
		//成員內部類的方法
		public void info() {
			System.out.println("當前牛腿顏色是:"+this.color+",高:"+this.length);
			System.out.println("本牛腿所在奶牛重:"+Cow.this.weight);//如果不寫Cow.this.,編譯器將自動添加
			//直接訪問外部類中private修飾的成員變量weight
			//weight引用了創建內部類實例對象的外部類的實例對象的私有數據域。
		}
	}
	public void test() {
		//CowLeg類的實例對象是在Cow類的實例方法中創建的。
		//所以,在創建CowLeg內部類的實例對象之前,必先創建Cow外部類的實例對象。
		CowLeg b=new CowLeg(1.12,"黑白相間");
		//如果不寫this.編譯器將自動添加。
		//CowLeg b=this.new CowLeg(1.12,"黑白相間");
		b.info();
	}
	public static void main(String[] args) {
		Cow a=new Cow(378.9);//創建外部類Cow的實例對象
		a.test();
		/*Cow.CowLeg d=new Cow(378.9).new CowLeg(1.12,"黑白相間");//創建內部類CowLeg的實例對象
		d.info();*/
	}
}

 

3.2 靜態內部類 

3.2.1 概念理解 

將成員內部類的使用再深入限制一步,假如內部類的實例對象不需要引用外部類的實例對象,只是將一個類隱藏在另外一個類的內部,那麼就可以將該內部類靜態化。在外部類的內部,定義的靜態的內部類,叫靜態內部類(或叫嵌套類)。提示:靜態內部類在實際工作中用的並不是很多。 

① 外部類的靜態成員對靜態內部類可見。靜態內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
② 外部類對靜態內部類可見。在內部類中可以使用 new 生成外部類的實例對象。
③ 外部類的實例成員對靜態內部類不可見。靜態內部類的實例對象,沒有外部類的實例對象的引用,所以不能訪問外部類的實例。 

④ 靜態內部類對外部類可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類中可以使用 new 生成內部類的實例對象。

⑤ 靜態內部類可以使用 public、protected、default或private 訪問修飾符進行訪問控制,內部類能夠隱藏起來,不爲同一包的其它類訪問。一般當做成員變量設置爲 private。
⑥ 靜態內部類是靜態的。所以在靜態內部類中,可以定義靜態字段、靜態方法和靜態內部類;也可以定義非靜態字段、非靜態方法和非靜態內部類。

與成員內部類相比,靜態內部類沒有編譯器自動添加的實例字段和構造器參數。即靜態內部類的實例是不可以訪問外部類的實例成員。

3.2.2 應用舉例 

public class StaticInnerClassDemo {
	private int a=5;//實例變量
	private static int b=9;//靜態變量
	private static class StaticInnerClass {
		private static int number=28;//靜態內部類裏可以包含靜態成員
		public int find1() {
			//System.out.println(a);報錯!!!因爲靜態內部類無法直接訪問外部類的實例變量
			return b;
		}
		public static int find2() {
			return number;
		}
	}
	public static void main(String[] args) {
		StaticInnerClassDemo m=new StaticInnerClassDemo();//創建外部類的實例對象
		//外部類中,a爲實例字段,由對象調用;b爲靜態字段,由類調用
		System.out.println("外部類的成員變量的值分別爲:"+m.a+","+StaticInnerClassDemo.b);
		StaticInnerClass d=new StaticInnerClass();//創建靜態內部類的實例對象
		//靜態內部類的實例對象調用實例方法,靜態內部類直接調用靜態方法
		System.out.println("靜態內部類的成員變量的值分別爲:"+d.find1()+","+StaticInnerClass.find2());
	}
}

 

3.3 局部內部類(概念理解)

在外部類的方法中定義的非靜態的內部類,叫局部內部類。(因爲內部類可以訪問外部類方法的形參和局部變量而得此名)可以分爲:①在外部類的實例方法內部的局部內部類;②在外部類的靜態方法內部的局部內部類。
提示:在實際開發中很少使用局部內部類,只是因爲局部內部類的作用域很小,只能在當前方法中使用。 

3.3.1 外部類實例方法中的局部內部類(概念理解)

 

① 實例方法的形參對局部內部類可見。局部內部類的實例對象,可以有外部類的實例方法的形參的字段,可以訪問外部類實例方法的形參。這些形參在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。
② 實例方法的局部變量對局部內部類可見。局部內部類的實例對象,可以有外部類的實例方法的局部變量的字段,可以訪問外部類實例方法的局部變量。這些局部變量在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。 

③ 外部類的實例成員對局部內部類可見。局部內部類的實例對象,有外部類的實例對象的引用,所以可以訪問外部類實例的所有成員(包括 private 私有的)。
④ 外部類的靜態成員對局部內部類可見。局部內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
⑤ 外部類對局部內部類可見。在內部類中可以使用 new 生成外部類的實例對象。 

⑥ 局部內部類對外部類的實例方法可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類的實例方法中可以使用 new 生成局部內部類的實例對象。 

⑦ 局部內部類不能使用 public、protected或private 訪問修飾符進行訪問控制,它的作用域被限定在聲明該局部內部類的方法塊中,所以在外部類的方法中不可以使用 new 生成局部內部類的實例對象。除了局部內部類所在的外部類方法,沒有任何方法知道內部類的存在。

⑧ 局部內部類是非靜態的。所以在局部內部類中,不能定義靜態字段、靜態方法和靜態內部類;但是可以定義非靜態字段、非靜態方法和非靜態內部類。

3.3.2 外部類實例方法中的局部內部類(應用舉例)

class InstanceLocalOut {
	private int age=12;
	public void Print(int x) {//如下是實例方法中的局部內部類
		int m=8;
		class InstanceLocalIn {//在實例方法中定義一個局部內部類
			public void inPrint() {//局部內部類的實例方法
                                //直接訪問外部類的private修飾的成員變量age
				System.out.println("局部內部類獲取外部類的實例成員:"+age);
                                //直接訪問外部類實例方法的形參x
				System.out.println("局部內部類獲取外部類實例方法的形參:"+x);
                                //直接訪問外部類實例方法的局部變量m
				System.out.println("局部內部類獲取外部類實例方法的局部變量:"+m);
			}
		}
		InstanceLocalIn b = new InstanceLocalIn();//創建一個局部內部類的實例對象
		b.inPrint();//調用該局部內部類中的實例方法
	}
}
public class InstanceLocalInnerClass {
	public static void main(String[] args) {
		InstanceLocalOut a=new InstanceLocalOut();//創建一個外部類的實例對象
		a.Print(3);//通過實例對象調用外部類中的實例方法
	}
}

3.3.3 外部類靜態方法中的局部內部類(概念理解)

外部類靜態方法中的局部內部類除了不能訪問外部類的實例外(因爲它沒有外部類的實例對象的引用),與實例方法中的局部內部類相同。

① 靜態方法的形參對局部內部類可見。局部內部類的實例對象,可以有外部類的靜態方法的形參的字段,可以訪問外部類靜態方法的形參。這些形參在 JDK8 之前必須被聲明爲final,但在 JDK8 中就不需要了。
② 靜態方法的局部變量對局部內部類可見。局部內部類的實例對象,可以有外部類的靜態方法的局部變量的字段,可以訪問外部類靜態方法的局部變量。這些局部變量在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。 

③ 外部類的靜態成員對局部內部類可見。局部內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
④ 外部類對局部內部類可見。在局部內部類中可以使用 new 生成外部類的實例對象。
⑤ 外部類的實例成員對局部內部類不可見。局部內部類的實例對象,沒有外部類的實例對象的引用,所以不可以訪問外部類實例的所有成員。

⑥ 局部內部類對外部類的靜態方法可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類的靜態方法中可以使用 new 生成內部類的實例對象。

⑦ 局部內部類不能使用public、protected或private訪問修飾符進行訪問控制,它的作用域被限定在聲明該局部內部類的方法塊中。

⑧ 局部內部類是非靜態的。所以在局部內部類中,不能定義靜態字段、靜態方法和靜態內部類;但是可以定義非靜態字段、非靜態方法和非靜態內部類。

3.3.4 外部類靜態方法中的局部內部類(應用舉例)

class StaticLocalOut {
	private int age=12;
	public static void print(int x) {
		int m=8;
		class StaticLocalIn {
			public void inPrint() {
				//System.out.println(age);靜態方法中的局部內部類不能訪問外部類的實例
				//直接訪問外部類靜態方法的形參x
				System.out.println("局部內部類獲取外部類靜態方法的形參:"+x);
				//直接訪問外部類靜態方法的局部變量m
				System.out.println("局部內部類獲取外部類靜態方法的局部變量:"+m);
			}
		}
		StaticLocalIn a=new StaticLocalIn();//創建一個局部內部類的實例對象
		a.inPrint();//調用該局部內部類中的實例方法
	}
}
public class StaticLocalInnerClass {
	public static void main(String[] args) {
		StaticLocalOut.print(3);//該外部類自身去調用其中的靜態方法
	}
}

 

3.4 匿名內部類(概念理解) 

1. 將局部內部類的使用再深入一步,假如只創建這個類的一個對象,就不必命名了。從使用上講,匿名內部類和局部內部類的區別是:一個是匿名的,另一個是命名的,其它均相同。(匿名的含義是由編譯器自動給內部類起一個內部名稱)
2. 在外部類的方法中,定義的非靜態的沒有類名的內部類,叫匿名內部類。
3. 匿名內部類適合只需要使用一次的類,當創建一個匿名內部類時會立即創建該類的一個實例對象,匿名類不能重複使用。可以分爲:在外部類的實例方法內部的匿名內部類,在外部類的靜態方法內部的匿名內部類。 

3.4.1 外部類實例方法中的匿名內部類(概念理解)

① 實例方法的形參對匿名內部類可見。匿名內部類的實例對象,可以有外部類的實例方法的形參的字段,可以訪問外部類實例方法的形參。這些形參在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。
② 實例方法的局部變量對匿名內部類可見。匿名內部類的實例對象,可以有外部類的實例方法的局部變量的字段,可以訪問外部類實例方法的局部變量。這些局部變量在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。 

③ 外部類的實例成員對匿名內部類可見。匿名內部類的實例對象,有外部類的實例對象的引用,所以可以訪問外部類實例的所有成員(包括 private 私有的)。
④ 外部類的靜態成員對匿名內部類可見。匿名內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
⑤ 外部類對局部內部類可見。在匿名內部類中可以使用 new 生成外部類的實例對象。 

⑥ 匿名內部類對外部類該實例方法可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類中實例方法可以使用 new 生成內部類的實例對象。

⑦ 匿名內部類不能使用 public、protected或private 訪問修飾符進行訪問控制,它的作用域被限定在聲明該匿名內部類的方法塊中。

⑧ 由於構造器的名字必須與類名相同,而匿名類無類名,所以匿名內部類不能有構造器。取而代之的是將構造器參數傳遞給父類構造器。
⑨ 匿名內部類是非靜態的。所以在匿名內部類中,不能定義靜態字段、靜態方法和靜態內部類;但是可以定義非靜態字段、非靜態方法和非靜態內部類。

3.4.2 外部類實例方法中的匿名內部類(應用舉例)

class InstanceAnonOut {
	private int age=12;
	public void Print(int x) {//如下是實例方法中的匿名內部類
		int m=8;
		(new InstanceAnonOut() {//創建匿名內部類
			public void inPrint() {//匿名內部類的實例方法
				//直接訪問外部類的private修飾的成員變量age
				System.out.println("匿名內部類獲取外部類的實例成員:"+age);
				//直接訪問外部類實例方法的形參x
				System.out.println("匿名內部類獲取外部類實例方法的形參:"+x);
				//直接訪問外部類實例方法的局部變量m
				System.out.println("匿名內部類獲取外部類實例方法的局部變量:"+m);
			}
		}).inPrint();
	}
}
public class InstanceAnonInnerClass {
	public static void main(String[] args) {
		InstanceAnonOut a=new InstanceAnonOut();//創建一個外部類的實例對象
		a.Print(3);//通過實例對象調用外部類中的實例方法
	}
}

3.4.3 外部類靜態方法中的匿名內部類(概念理解)

靜態方法中的匿名內部類除了不能訪問外部類的實例外(因爲它沒有外部類的實例對象的引用),與實例方法中的匿名內部類相同。 

① 靜態方法的形參對匿名內部類可見。匿名內部類的實例對象,可以有外部類的靜態方法的形參的字段,可以訪問外部類靜態方法的形參。這些形參在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。
② 靜態方法的局部變量對匿名內部類可見。匿名內部類的實例對象,可以有外部類的靜態方法的局部變量的字段,可以訪問外部類靜態方法的局部變量。這些局部變量在 JDK8 之前必須被聲明爲 final,但在 JDK8 中就不需要了。

③ 外部類的靜態成員對匿名內部類可見。匿名內部類可以訪問外部類的所有靜態成員(包括 private 私有的)。
④ 外部類對匿名局部內部類可見。在局部內部類中可以使用 new 生成外部類的實例對象。
⑤ 外部類的實例成員對匿名內部類不可見。匿名內部類的實例對象,沒有外部類的實例對象的引用,所以不可以訪問外部類實例的所有成員(包括 private 私有的)。

⑥ 內部類對外部類的靜態方法可見。因爲編譯後的內部類至少是包內,其構造器至少是包內,所以在外部類的靜態方法中可以使用 new 生成內部類的實例對象。

⑦ 匿名內部類不能使用 public、protected或private 訪問修飾符進行訪問控制,它的作用域被限定在聲明該匿名內部類的方法塊中。

⑧ 匿名內部類不能有構造方法。因爲匿名類無類名,所以無法寫構造方法。取而代之的是將構造器參數傳遞給超類構造器。
⑨ 匿名內部類是非靜態的。所以在匿名內部類中,不能定義靜態字段、靜態方法和靜態內部類;但是可以定義非靜態字段、非靜態方法和非靜態內部類。

3.4.4 外部類靜態方法中的匿名內部類(應用舉例)

class StaticAnonOut {
	private int age=12;//外部類的實例成員
	public static void print(int x) {//如下是靜態方法中的匿名內部類
		int m=8;
		(new StaticAnonOut() {
			public void inPrint() {
				//System.out.println(age);靜態方法中的匿名內部類不能訪問外部類的實例
				//直接訪問外部類靜態方法的形參x
				System.out.println("匿名內部類獲取外部類靜態方法的形參:"+x);
				//直接訪問外部類靜態方法的局部變量m
				System.out.println("匿名內部類獲取外部類靜態方法的局部變量:"+m);
			}
		}).inPrint();
	}
}
public class StaticAnonInnerClass {
	public static void main(String[] args) {
		StaticAnonOut.print(3);//該外部類自身去調用其中的靜態方法
	}
}


以上,就是博主在學習過程中,總結的 Java 語言中,四種內部類(成員內部類、靜態內部類、局部內部類和匿名內部類)的相關內容,希望對大家的學習和理解有所幫助!!!😁😁😁 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章