1. 程式人生 > >Java泛型詳解(透徹)

Java泛型詳解(透徹)

amp pan 同時 dog extend dem get方法 多個 條件

定義

Java中的泛型在JavaSE5中引入。
所謂泛型,即參數化類型。就是說,類型是以參數的方式傳入泛型類。
例如:

ArrayList<Integer> aaryList = new ArrayList<Integer>();

那麽,類型參數就是Integer。

緣由

為什麽要引入泛型呢,得看在沒有泛型的情況下會存在什麽樣的問題。看下面這個非常常見的例子:

ArrayList a = new ArrayList();
a.add(1);
a.add("2");
for(int i=0; i<a.size(); i++)
{
  String strTmp = (String)a.get(i);
}

點擊運行,啊哦,ClassCastException!

看出來了吧,問題就是類型不安全,給你一個ArrayList,很難直接獲取內部元素的類型,容易引發錯誤。

泛型設計中的坑

JavaSE5中怎麽引入泛型呢?
原先沒有泛型的時候使用方式是這樣:

ArrayList a = new ArrayList();

如果老的方式直接不支持了,那麽基於Java做得那麽多類庫咋辦,豈不是沒法直接遷移至新版本的jdk?
No, No, No~這樣會損失很多用戶~
算了算了,非泛型和泛型兩種方式並存吧。
那麽泛型怎麽在以前的基礎上加入呢?
想來想去,還是在編譯器保證吧!最簡單!
ok,存在什麽問題呢?看個demo:

class GenericsA<T>
{
    T t = new T(); // Error
}

天真的我以為可以這麽搞,然而我卻被告知: Type parameter ‘T‘ cannot be instantiated directly.

好吧,原來這個T只是個占位符而已類型信息並不會在運行時保存!類型信息會被擦除!

類型擦除

泛型的類型信息會被擦除,因此不要奢想在泛型類內部利用類型信息做任何事情
至於使用泛型類的地方,編譯器來做類型檢查,但是也不足夠安全。
看個例子:

public static void main(String[] args) {
	ArrayList arrayList = new ArrayList<String>();
	arrayList.add("A");
	arrayList.add("C");
	arrayList.add(3);

	String tmp = (String)arrayList.get(2);
}

啊哦,又是ClassCastException。
別忘記,ArrayList內部沒有類型信息,在把ArrayList<String>轉為ArrayList非泛型列表之後,連編譯器也無法正常類型檢查了!

類型邊界

在編譯器類型檢查之前,泛型的類型信息會被擦除至某個邊界。
普通泛型class ClassA<T>{}中類型會被擦除至Object

class A<T>
{
	T item;
	public A(T item)
	{
		this.item = item;
	}

	void test()
	{
		//此處,item只能調用屬於Obeject類的方法
	}
}

通過泛型類型上設置邊界,我們可以實現類型的限定:

class Animal{
	void talk(){}
}

class B<T extends Animal>
{
	T item;
	public A(T item)
	{
		this.item = item;
	}

	void test()
	{
		//此處,item可以調用屬於Animal類的方法
		item.talk();
	}
}

甚至,可以設置多個邊界:

interface Eatable {void eat();}
interface Drinkable {void drink();}
class B<T extends Dog & Cat>
{
	T item;
	void test()
	{
		//此處,item可以調用屬於Eatable和Drinkable的方法
		//不過,前提是,item同時是Eatable和Drinkable
		//(這裏只能通過多個接口實現的方式,或者一個基類和多個接口的方式,因為你無法使一個類繼承自多個父類)
	}
}

通配符

所謂通配符,作用就是匹配多種類型。

1. 上界通配符: < ? extends B >

意思是,可以匹配類型A及其所有子類,即類型的上界是B,無下界。
舉例子:

class A
{
	public void print()
	{
		System.out.println("A");
	}
}
class B extends A
{
	@Override
	public void print()
	{
		System.out.println("B");
	}
}
class C extends B
{
	@Override
	public void print()
	{
		System.out.println("C");
	}
}

class MyArrayList extends ArrayList<? extends B>
{
}

那麽MyArrayList可以被賦值時:

MyArrayList myArrayList = new ArrayList<A>(); //ERROR
MyArrayList myArrayList = new ArrayList<B>(); //OK
MyArrayList myArrayList = new ArrayList<C>(); //OK

再來看下元素讀寫情況。

編譯器內心獨白:

  • >>>> 既然設了上界通配符,那麽我MyArrayList中的元素實際類型可能為B和B的任何子類,那麽為了確保類型安全,只有同時繼承自B和所有B的子類的類型才能add。
  • >>>> 嗯?好像沒有任何一種類型可用滿足以上條件
  • >>>> 好吧,上界通配符泛型中,myArrayList不接受add方法
  • >>>> 哦,對了,任何以泛型類型為參數的方法我都不接受

好吧,經歷了編譯器的內心一番搏鬥,所有以泛型類型為參數的方法都沒法使用了:

myArrayList.add(new B()); //ERROR
myArrayList.add(new C()); //ERROR
myArrayList.set(0, new B()); //ERROR

但是,get()、remove()方法等方法還是可以用的。

2. 下界通配符 ? super B

意思是,可以匹配類型B及其所有基類,即類型的下界是B,上界是Object。
接著舉例子:

class MyArrayList extends ArrayList<? super B>
{}

那麽:

MyArrayList myArrayList = new ArrayList<A>(); //OK
MyArrayList myArrayList = new ArrayList<B>(); //OK
MyArrayList myArrayList = new ArrayList<C>(); //ERROR
MyArrayList myArrayList = new ArrayList<Object>(); //OK

上至Object,下至B,所有的實例都可以賦值給泛型為<?super B>的myArrayList。

再次看下元素讀寫情況。

編譯器:

  • >>>> 既然設了下界通配符,那麽我MyArrayList中的元素實際類型可能為B和B的任何父類,那麽為了確保類型安全,只有同時是B以及B的所有父類的類型實例才能add。
  • >>>> 想一下,哪些類型符合條件呢?恩,好像所有B和B子類的實例都滿足條件。例如。new B()既是B又是Object,new C()一樣可以向上轉型至B和Obejct....
  • >>>> 當然,所有B及其子類的實例都可以add進去
  • >>>> 那麽,get方法?
  • >>>> myArrayList內部可能的類型那麽多(B及其所有父類),get的返回值類型咋辦呢,算了,用通用的Object吧,真實類型讓那些碼農自己判斷去吧!

Java泛型詳解(透徹)