探索JAVA——內部類

內部類大法

最近學到了內部類的範圍,但是上課的老師只是一筆帶過,所以很多內容還是很模糊,所以打開了JAVA編程思想,理解後還是寫篇博客便於日後的查看,菜鳥要抓緊成長啊!加油,給自己打氣!!!

在接下來的篇幅中主要會介紹普通的內部類和靜態嵌套內部類,其實聽名字能容易的聯想到成員變量,同時它們也是非常相似的,可以藉助成員變量來理解不同的內部類

內部類

什麼是內部類呢?可以將一個類的定義放在另一個類的定義內部,這就是內部類。內部類是一種非常有用的特性,因爲它允許你把一些邏輯相關的類組織在一起,並控制位於內部的類的可視性(它用來測試類和放在接口中的時候是真的妙極了啊~再次感嘆JAVA相比於c、c++於操作上爽很多啊)

普通的內部類

如前文所說,可以將它按照實例變量理解。
1.如果想從外部類的非靜態方法之外的任何位置創建某個內部類的對象,那麼必須具體的致命這個對象的類型:OuterClassName.InnerClassName
2.普通的內部類不僅僅只是一種名字隱藏和組織代碼的模式;它還能在生成一個內部類的對象時,與製造它的外圍對象之間有一種聯繫,所以它能訪問外圍對象的所有成員,而且是不需要任何條件的訪問!

下面我們先創建一個普通的內部類來感受一下:

例子一:

/*
 * 名字隱藏和組織代碼的模式
 * */
import javax.xml.namespace.QName;

public class Parcel1 {
	class Contents {
		private int i = 11;
		public int value() { return i; }
	}
	class Destination {
		private String lable;
		public Destination(String whereTo) {
		lable = whereTo;
		}
		String readLabel() {return lable;}
	}
	//	Outer定義方法返回Inner實例
	public Contents contents() {
		return new Contents();
	} 
	public Destination to(String s) {
		return new Destination(s);
	}
	//	利用ship方法同時返回兩個Inner的實例
	public void ship(String dest) {
		Contents contents = contents();
		Destination d = to(dest);
		System.out.println(d.readLabel());
	}
	public static void main(String[] args) {
		Parcel1 p = new Parcel1();
		p.ship("Tasmania");
		Parcel1 q = new Parcel1();
		Parcel1.Destination d = q.to("Birneo");
		Parcel1.Contents c = q.contents();
	}
}

例子二:

/*當生成一個內部類的對象時,次對象與製造它的外圍對象之間就有了一種聯繫,所以他能訪問外圍對象 的所以成員,二不需要任何特殊條件
 * */

//	創建Selector接口,可以實現(end()、current()、next())
interface Selector{
	boolean end();
	Object current();
	void next();
}

public class Saquence {
	private Object[] items;
	private int next = 0;
	//	給數組一個初始化大小
	public Saquence(int size) { items = new Object[size]; }
	
	//	給數組中添加元素
	public void add(Object x) {
		if(next<items.length) 
			items[next++] = x; 
	}
	private class SeqenceSelector implements Selector {
		private int i;
		//	檢查序列是否到末尾
		public boolean end(){
			return i == items.length;
		}
		public Object current() {
			return items[i];
		}
		public void next() {
			if(i<items.length)
				i++;
		}
	}
	//	外部類的方法,返回一個內部類
	public Selector selector() {
		return new SeqenceSelector();
	}
	public static void main(String[] args) {
		Saquence saquence = new Saquence(10);
		for (int i = 0; i < 10; i++) {
			saquence.add(Integer.toString(i));
		}
		//	利用外部類創建一個內部類
		Selector selector = saquence.selector();
		while(!selector.end()) {
			System.out.print(selector.current()+" ");
			selector.next();
		}
	}
}

在例二中,Sequence類只是一個固定大小的Object數組,以類的形式包裝了起來,在還有空間的情況下可以調用add()在序列末尾添加新的Object。Selector允許你檢查序列是否到末尾了(end())、訪問當前對象(current())、以及移到序列中的下一個對象(next())。因此Selector是一個接口,所以別的類可以按他們自己的方式來實現接口,並且另外的方法能以此接口爲參數,來生成更加通用的反碼。內部類可以訪問其外圍類的方法和字段,就像自己擁有他們似的。

內部類是如何做到自動擁有對其外部類的訪問權限的呢?
當外圍類的對象創建了一個內部類對象時,此內部類對象必定會祕密地捕獲一個紙箱那個外圍類對象的引用。然後當你在訪問外圍類對象的成員時,就是用哪個引用來選擇外圍類的成員。(當然編譯器會幫你處理這些細節)

使用.this 和 .new

.this
如果你需要生成對外部類對象的引用,可以使用外部類的名字後面緊跟.this。這樣產生的引用自動地具有正確的類型。

public class DotThis {
	void f() { System.out.println("DotThis.f()"); }
	public class Inner {
		public DotThis outer() {
			//	生成對外部類對象的引用,產生的引用自動地具有正確的類型
			return DotThis.this;	
		}
	}
	public Inner inner() { return new Inner(); }
	public static void main(String[] args) {
		DotThis dt = new DotThis();
		DotThis.Inner dti= dt.inner();
		//	利用.this找到外部類的引用從而正確的使用控制外部類的遙控器
		dti.outer().f();
	}
}

你必須使用外部類的對象來創建該內部類對象,實際上,在擁有外部類對象之前是不可能創建內部類對象的,這是因爲內部類對象會暗暗地連接到創建它的外部類對象上。(但是,如果你創建的是嵌套類(靜態內部類)就不要對外部對象的引用)

.new

/*除了在外部類中用方法返回內部類對象外,還可以使用.new的方式去創建其某個內部類的對象
 * */
public class DotNew {
	public class Inner { }
	public static void main(String[] args) {
		DotNew dNew = new DotNew();
		DotNew.Inner dni = dNew.new Inner();
	}
}
//~OK,Good!
/*在擁有外部類對象之前是不可能創建內部類對象的——成員變量類
 * 嵌套類(靜態內部類)不需要對外部類對象的引用——靜態變量類
 * */
 

補充一個點:
private:表示除了外部類,其他類都不能訪問該內部類
protected:表示只有外部類和它的子類能夠訪問

匿名類

先看段代碼感受一下:


class Wrapping {
	private int i;
	public Wrapping(int x) {i = x;}

	public int value() {
		return i;
	}
}

public class Parcel7b {
	public Wrapping wrapping(int x) {
		return new Wrapping(x) {
			public int value(){
				return super.value()*47;
			}
		};
	}
	public static void main(String[] args) {
		Parcel7b p = new Parcel7b();
		Wrapping w = p.wrapping(10);
		System.out.println(w.value());
	}
}

wrapping(int x)方法將返回值的生成與表示這個返回值的類的定義結合在一起!另外,你可以發現這個類是沒有名字的,它看起來似乎是你正在要創建一個Wrapping對象。但是然後(在到達語句結束的分號之前)你卻說:“等一下,我想在這插入一個類的定義”。
你可以注意到,*匿名內部類末尾的分號,並不是用來標記次內部類結束的。是加上,它標記的事表達式的結束,只不過這個表達式正巧包含了匿名內部類。因此,這與別的地方使用的分號是一致的

因此上面的Parcel7b類等價於:

public class Parcel7b {
	public class ParWrapping extends Wrapping{
		public int value(){
			return super.value()*47;
		}
	}
	public Wrapping wrapping() {
		return new ParWrapping();
	}
	public static void main(String[] args) {
		Parcel7b p = new Parcel7b();
		Wrapping w = p.wrapping(10);
		System.out.println(w.value());
	}
}

使用匿名類其外部定義的對象需要final

interface Destination{
	String readLabel();
}
public class Parcel{
	public Destination destination(final String dest){
		return new Destination(){
			private String label = dest;
			public String readLabel(){
				return label;
			}
		};
	}
	public static void main(String[] args){
		Parcel p = new Parcel();
		Destination d = p.destination("hello");
	}
}

如果定義一個匿名類,並且希望它使用有個在其外部定義的對象,那麼編譯器會要求其參數引用是final的。

這裏所說的“外部定義的對象”,指的是所有外來的對象,包括外部方法的形參、局部變量、基本類型或自定義類型等。

這裏之所以只能用final修飾的參數,是變量作用域的原因。雖然匿名內部類被定義在方法內部,但匿名內部類是單獨的個體,編譯時隨外部類一起被編譯成爲Outer$1.class文件,並不是方法被調用時纔會被執行。方法中的局部變量只是在方法被調用時被創建在棧內存中,調用完畢會自動清空棧,所以,匿名內部類要想使用方法內部的變量,只能將該變量用final修飾,即定義爲常量。

匿名類相比於正規的繼承相比受限,因爲匿名內部類既能擴展類,也能實現接口,但是兩者不能兼得;如果是實現接口,也只能實現一個接口

嵌套類

如果不需要內部類對象與其外圍類對象之間有聯繫,可以將內部類聲明爲static,通常被稱爲嵌套類或者靜態類。當內部類是static時就不會保存指向外圍對象的引用了。(普通的內部類不能有static數據和static字段,也不能含有嵌套類)。嵌套類類似於一個static方法。

public class TestBed {
	public void f() { System.out.println("f()"); }
	public static class Tester {
		public static void main(String[] args) {
			//	不能直接使用外部類的非static元素,需要先創建外部類的對象才能使用(感覺也不是很影響啦。。。)
			TestBed t = new TestBed();
			t.f();
		}
	}
}

接口內部的類

嘿嘿,在正常情況下,不能在接口內部放置任何代碼,但嵌套類可以作爲接口的一部分,你放到接口中的任何類都自動地施public和static的。因此類是static的,只是嵌套類之餘接口的命名空間內。你甚至可以在內部類中實現其外圍接口。

public interface ClassInInterface {
	void howdy();
	class Test implements ClassInInterface{
		public void howdy() {
			System.out.println("Howdy!");
		}
	}
	public static void main(String[] args) {
		new Test().howdy();
	}
}

什麼時候能用到它呢?

1. 如果你想要創建某些公共代碼,使得他們可以被某個及餓哦口的所以不同實現所共用,那麼使用接口內部的嵌套類會很方便。
2. 每個類都做好寫一個main()方法來測試類,但是那樣會造成必須帶着那些已編譯過的額外代碼。而使用嵌套類的話,雖然生成了一個獨立的類(如:TestBed$ Tester)
,但是不必在發佈的產品中包含它,在產品打包前可以簡單地刪除TestBed$ Tester.class

接口內部類還是很奇特的

interface ClassInInterfac {
	void howdy();
	class Test implements ClassInInterfac{
		public void howdy() {
			System.out.println("Howdy");
		}
	}
	public static void main(String[] args) {
		new Test().howdy();
	}
}
public class A {
	public static void main(String[] args) {
		ClassInInterfac.Test t = new ClassInInterfac.Test();
		t.howdy();
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章