1. 程式人生 > >Java開發筆記(五十四)內部類和嵌套類

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

pro get print 資源 sys 文件 stat 解決 運用

通常情況下,一個Java代碼文件只定義一個類,即使兩個類是父類與子類的關系,也要把它們拆成兩個代碼文件分別定義。可是有些事物相互之間密切聯系,又不同於父子類的繼承關系,比如一棵樹會開很多花朵,這些花兒作為樹木的一份子,它們依附於樹木,卻不是樹木的後代。花朵不但擁有獨特的形態,包括花瓣、花蕊、花萼等,而且擁有完整的生命周期,從含苞欲放到盛開綻放再到雕謝枯萎。這樣一來,倘若把花朵抽象為花朵類,那麽花朵類將囊括花瓣、花蕊、花萼等成員屬性,以及含苞、盛開、雕謝等成員方法。既然花朵類如此規整,完全可以定義為一個class,但是花朵類又依附於樹木類,說明它不適合從樹木類獨立出來。
為了解決這種依附關系的表達問題,自然就得打破常規思維,其實Java支持類中有類,在一個類的內部再定義另外一個類,仿佛新類是已有類的成員一般。一個類的成員包括成員屬性和成員方法,還包括剛才說的成員類,不過“成員類”的叫法不常見,大家約定俗成的叫法是“內部類”,與內部類相對應,外層的類也可稱作“外部類”。仍舊以前述的樹木類和花朵類為例,如今可在樹木類的內部增加定義花兒類,就像下面代碼那樣:

//演示內部類的簡單定義
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 內部類的名稱(...)”。下面是外部調用內部類的具體代碼例子:

		// 先創建外部類的實例,再基於該實例去創建內部類的實例
		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。於是在內部類裏面終於能夠區分內部類和外部類的同名屬性了,詳細的區分代碼如下所示:

		// 該方法訪問內部類自身的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的歸屬類名。

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

//演示嵌套類的定義
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開發筆記(序)章節目錄》

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