1. 程式人生 > >Java筆記(一) 協變性、陣列與泛型

Java筆記(一) 協變性、陣列與泛型

前言

在開始前,我們先看一段有點“誤導性”的程式碼,下面的程式碼在編譯時不會產生任何異常。

package test;

public class Test {
	private interface Shape{
	}
	private class Square implements Shape{	
	}
	private class Circle implements Shape{
	}
	public static void main(String[] args) {
		Shape[] arr=new Square[5];
		arr[0]=new Test().new Circle();
	}
}

但是如果你執行,則會發生 :

Exception in thread "main" java.lang.ArrayStoreException: test.Test$Circle
    at test.Test.main(Test.java:17)
 

協變性

發生上面情形的原因,便是Java陣列中的bug,編譯器在編譯時,並不能預知執行時的情況,所以它能判斷的,只有一個,也即“套皮”是否是對的,也就是,Shape這個皮能否套下Square這個Object,如果它能,編譯也就通過了。在非陣列的情況下,它也就通過了,否則我們也無法用介面來承載類了。

但如果是陣列,那情況就變得複雜了起來,Java中的陣列,具有協變性(

covariance),也即能夠使用比原始指定的派生型別的派生程度更大(更具體的)的型別;以上面為例,Circle IS-A Shape;Square[] IS-A Shape[];Circle是能夠放進Shape[]這個皮套中的,所以從編譯器的角度來說,它也是能放進Square[]中的,但如果真的這麼幹,就會出錯。這也就是陣列的bug。而且使用陣列的集合也是協變的:

public class Test {
	private interface Shape{
	}
	private class Square implements Shape{	
	}
	private class Circle implements Shape{
	}
	public static void main(String[] args) {
		ArrayList arraylist=new ArrayList();
		arraylist.add(new Test().new Square());
		arraylist.add(new Test().new Circle());
		Circle circle=(Circle)arraylist.get(0);//報錯
	}
}

為了解決這個Bug,讓問題得以在編譯器階段就被發現,Java在Java5後,引入了泛型的概念。

泛型

(一)泛型引入後的區別

引入了泛型以後,由於泛型不再具有協變性,ArrayList<Square>套皮ArrayList<Shape>將失敗,上文提到的問題在編譯階段就會被發現。

(二)以前的泛型實現

其實在泛型引入前,泛型也是有其實現方式的,其一便是Object作為引數傳入的方式,所有的類都繼承自Object,所以我們可以這樣操作。其二是用介面實現泛型。

在那之前,先引入一個東西(其實可以單開寫個幾萬字的),物件是封裝了操作的一組資料,其封裝方式不同,所以當以錯誤的方式開啟時,就會出錯。我們傳遞一個類時,無論它是Square還是Circle,只要開啟方式(強轉回去的時候)正確,就不會有事。以泛型方法舉例:

class Prototype{
	public void echoInfo(){
		System.out.println("this is prototype");
	}
}
class Experimental extends Prototype{
	
}
public class Test {
	
	public static void main(String[] args) {
		printItem(new Experimental ());
	}
	public static void printItem(Object a){
		Prototype EC=(Prototype)a;
		ec.echoInfo();
	}
}

但如果這樣則會產生兩個問題:

  • 必須知道要強轉成什麼,而且無法控制傳入非Prototype的子類
  • 不轉的話,除了寥寥幾個toString等方法你調不了別的

但引入了泛型以後,可以控制傳入:

class Prototype{
	public void echoInfo(){
		System.out.println("this is prototype");
	}
}
class Experimental extends Prototype{
	
}

public class Test {
	
	public static void main(String[] args) {
		printItem(new Experimental());
	}
	public static <AnyType extends Prototype> void printItem(AnyType a){
		a.echoInfo();
	}
}

(三)冗長的傳入

但是引入泛型也非全然沒有缺點,JAVA中泛型不具有協變性,避免了陣列的bug,但是為了使得泛型更通用,泛型提出了萬用字元的概念:<? extends/supers Superclass>

如果一個類或者方法想傳入下面的ArrayList<Square>和ArrayList<Circle>,你需要這句話:

<AnyType extends Comparable<? super AnyType>>

參考資料:

資料結構與演算法分析(Java語言描述) Page 8