Java開發筆記(五十四)內部類和嵌套類

一般狀況下,一個Java代碼文件只定義一個類,即便兩個類是父類與子類的關係,也要把它們拆成兩個代碼文件分別定義。但是有些事物相互之間密切聯繫,又不一樣於父子類的繼承關係,好比一棵樹會開不少花朵,這些花兒做爲樹木的一份子,它們依附於樹木,卻不是樹木的後代。花朵不但擁有獨特的形態,包括花瓣、花蕊、花萼等,並且擁有完整的生命週期,從含苞欲放到怒放綻開再到凋謝枯萎。這樣一來,假若把花朵抽象爲花朵類,那麼花朵類將囊括花瓣、花蕊、花萼等成員屬性,以及含苞、怒放、凋謝等成員方法。既然花朵類如此規整,徹底能夠定義爲一個class,可是花朵類又依附於樹木類,說明它不適合從樹木類獨立出來。
爲了解決這種依附關係的表達問題,天然就得打破常規思惟,其實Java支持類中有類,在一個類的內部再定義另一個類,彷彿新類是已有類的成員通常。一個類的成員包括成員屬性和成員方法,還包括剛纔說的成員類,不過「成員類」的叫法不常見,你們約定俗成的叫法是「內部類」,與內部類相對應,外層的類也可稱做「外部類」。仍舊之前述的樹木類和花朵類爲例,現在可在樹木類的內部增長定義花兒類,就像下面代碼那樣:html

//演示內部類的簡單定義
public class Tree {
	private String tree_name;
	
	public Tree(String tree_name) {
		this.tree_name = tree_name;
	}
	
	public void sprout() {
		System.out.println(tree_name+"發芽啦");
		// 外部類訪問它的內部類,就像訪問其它類同樣,都要先建立類的實例,再訪問它的成員
		Flower flower = new Flower("花朵");
		flower.bloom();
	}
	
	// Flower類位於Tree類的內部,它是個內部類
	public class Flower {
		private String flower_name;

		public Flower(String flower_name) {
			this.flower_name = flower_name;
		}

		public void bloom() {
			System.out.println(flower_name+"開花啦");
		}
	}
}

從以上代碼可見,外部類裏面訪問內部類Flower,就像訪問其它類同樣,都要先建立類的實例,再訪問它的成員。至於在外面別的地方訪問這裏的外部類Tree,天然也跟先前的用法沒什麼區別。但是若是別的地方也想調用內部類Flower,那就沒這麼容易了,由於直接經過new關鍵字是沒法建立內部類實例的。只有先建立外部類的實例,才能基於該實例去new內部類的實例,內部實例的建立代碼格式形如「外部類的實例名.new 內部類的名稱(...)」。下面是外部調用內部類的具體代碼例子:java

		// 先建立外部類的實例,再基於該實例去建立內部類的實例
		TreeInner inner = new TreeInner("桃樹");
		// 建立一個內部類的實例,須要在new以前添加「外層類的實例名.」
		TreeInner.Flower flower = inner.new Flower("桃花");
		flower.bloom(); // 調用內部類實例的bloom方法

 

所謂好事多磨,引入內部類形成的麻煩不只僅一個,還有另外一個問題也挺棘手的。因爲內部類是外部類的一個成員類,所以兩者不可避免存在理論上的資源衝突。假設外部類與內部類同時擁有某個同名屬性,好比它倆都定義了名叫tree_name的樹木名稱字段,那麼在內部類裏面,tree_name到底指的是內部類自身的同名屬性,仍是指外部類的同名屬性呢?
從前面的類繼承文章瞭解到,一旦遇到同名的父類屬性、子類屬性、輸入參數,則編譯器採起的是就近原則,例如在方法內部優先表示同名的輸入參數,在子類內部優先表示同名的子類屬性等等。同理,對於同名的內部類屬性和外部類屬性來講,tree_name在內部類裏面優先表示內部類的同名屬性。考慮到避免混淆的緣故,也能夠在內部類裏面使用「this.屬性名」來表達內部類的自身屬性。但如此一來,內部類又該怎樣訪問外部類的同名屬性,確切地說,內部類Flower的定義代碼應當如何調用外部類TreeInner的tree_name字段?顯然這個問題足以讓關鍵字this人格分裂,明明身在TreeInner裏面,卻表明不了TreeInner。爲了拯救可憐的this,Java容許在this以前補充類名,從而限定此處的this究竟表明哪一個類。譬如「TreeInner.this」表示的是外部類TreeInner自身,而「TreeInner.this.tree_name」則表示TreeInner的成員屬性tree_name。因而在內部類裏面終於可以區份內部類和外部類的同名屬性了,詳細的區分代碼以下所示:this

		// 該方法訪問內部類自身的tree_name字段
		public void bloomInnerTree() {
			// 內部類裏面的this關鍵字指代內部類自身
			System.out.println(this.tree_name+"的"+flower_name+"開花啦");
		}

		// 該方法訪問外部類TreeInner的tree_name字段
		public void bloomOuterTree() {
			// 要想在內部類裏面訪問外部類的成員,就必須在this以前添加「外部類的名稱.」
			System.out.println(TreeInner.this.tree_name+"的"+flower_name+"開花啦");
		}

固然多數場合沒有這種外部與內部屬性命名衝突的狀況,故而在this前面添加類名純屬畫蛇添足,只有定義了內部類,而且內部類又要訪問外部類成員的時候,才須要顯式指定this的歸屬類名。htm

苦口婆心地囉嗦了許久,內部類的小脾氣總算搞定了。不料一波三折,以前說到其它地方調用內部類的時候,必須先建立外部類的實例,而後才能建立並訪問內部類的實例。這個流程實在繁瑣,比如我想泡一杯茉莉花茶,難道非獲得田裏種一株茉莉才行?很明顯這麼搞費時又費力,理想的作法是:只要屬於對茉莉花的人爲加工,而非緊密依賴於茉莉植株的天然生長,那麼這個茉莉花類理應削弱與茉莉類的耦合關係。爲了把新的類與類關係同外部類與內部類區分開來,Java容許在內部類的定義代碼前面添加關鍵字static,表示這是一種靜態的內部類,它無需強制綁定外部類的實例便可正常使用。
靜態內部類的正式稱呼叫「嵌套類」,外層類於它而言彷彿一層外套,有套沒套不會對嵌套類的功能運用產生實質性影響,套一套的目的僅僅表示兩者比較熟悉而已。下面是把Flower類改寫爲嵌套類的代碼定義例子,表面上只加了一個static:blog

//演示嵌套類的定義
public class TreeNest {
	private String tree_name;

	public TreeNest(String tree_name) {
		this.tree_name = tree_name;
	}

	public void sprout() {
		System.out.println(tree_name+"發芽啦");
	}
	
	// Flower類雖然位於TreeNest類的裏面,可是它被static修飾,故而與TreeNest類的關係比起通常的內部類要弱。
	// 爲了與通常的內部類區別開來,這裏的Flower類被叫作嵌套類。
	public static class Flower {
		private String flower_name;
		
		public Flower(String flower_name) {
			this.flower_name = flower_name;
		}

		public void bloom() {
			System.out.println(flower_name+"開花啦");
		}

		public void bloomOuterTree() {
			// 注意下面的寫法是錯誤的,嵌套類不能直接訪問外層類的成員
			//System.out.println(TreeNest.this.tree_name+"的"+flower_name+"開花啦");
		}
	}
}

如今Flower類變成了嵌套類,別的地方訪問它就會省點事,按照格式「new 外層類的名稱.嵌套類的名稱(...)」便可直接建立嵌套類的實例,沒必要多此一舉先建立外層類的實例。完整的調用代碼以下所示:繼承

	// 演示嵌套類的調用方法
	private static void testNest() {
		// 建立一個嵌套類的實例,格式爲「new 外層類的名稱.嵌套類的名稱(...)」
		TreeNest.Flower flower = new TreeNest.Flower("桃花");
		flower.bloom();
	}

 

正所謂有利必有弊,外部調用嵌套類卻是省事,嵌套類自身若要訪問外層類就不能爲所欲爲了。原先花朵類做爲內部類之時,經過前綴「外部類的名稱.this」即可訪問外部類的各項成員;現今花朵類搖身一變嵌套類,要訪問外層的樹木類再也不容易了,對嵌套類而言,外層類猶如一個熟悉的陌生人,想跟它打招呼就像跟路人打招呼同樣無甚區別,都得先建立對方的實例,而後才能經過實例訪問它的每一個成員。
迄今爲止,這裏已經介紹了好幾種的類,它們相互之間的關係各異,通俗地說,子類與父類之間是繼承關係,內部類與外部類之間是共存關係,嵌套類與外層類之間是同居關係。生命週期



更多Java技術文章參見《Java開發筆記(序)章節目錄資源

相關文章
相關標籤/搜索