1. 程式人生 > >《瘋狂Java講義(第4版)》-----第9章【泛型】

《瘋狂Java講義(第4版)》-----第9章【泛型】

把物件放入Java集合中,編譯的時候都會當成Object型別(執行時還是原來的實際型別),各種物件都可以放入集合,但是帶來的問題是從集合中取物件的時候可能要進行強制型別轉換了。如果集合中放了各種物件,特別是集合元素沒有順序的話,程式設計師只知道當初放進去很多型別的物件,取出的時候卻不知道哪個物件是什麼型別的了,不知道要強轉成什麼型別,結果是無法取出或者硬轉成可能錯誤的型別報異常。使用泛型的集合就是說,在定義一個集合的時候就指定這個集合中必須放置哪種型別的物件。 泛型可以在集合中使用,在其他方面也可以使用。

自定義使用泛型的類、介面

class A<T>{
	private T x;
	public A(T x){
		this.x = x;
	}
	public T getX(){
		return this.x;
	}
	
}

public class Hello{

	public static void main(String[] args){
		A<String> a1 = new A<String>("abc");
		System.out.println(a1.getX());//abc

		//注意用Integer而不是int,必須是引用型別		
		A<Integer> a2 = new A<>(2333333);//A<>這個尖括號省略了Integer就是所謂的“菱形”
		System.out.println(a2.getX());//2333333
	}
}

在上面的基礎上,如果有子類繼承自定義泛型的父類的話:

//這樣沒問題
class B extends A{
	
	public Object getX(){
		return "子類";
	}
}
//這樣是錯誤的,覆蓋父類的方法,下面的Object必須換成String
class B extends A<String>{
	
	public Object getX(){
		return "子類";
	}
}

靜態變數、靜態方法不得使用泛型。不管泛型指定何種引用型別,類或介面的型別不變

ArrayList<String> al1 = new ArrayList<>();
		ArrayList<Integer> al2 = new ArrayList<>();
		System.out.println(al1.getClass() == al2.getClass());//true

型別萬用字元

Java泛型的事蹟原則是,只要在編譯階段沒有出現警告,執行時就不會丟擲ClassCastException異常。

//這段程式碼是錯誤的,一旦指明泛型,必須嚴格一致。如果傳入的是存放String型別的List就報錯拋異常
//引數應該改成List list,不要指定泛型
public void test(List<Object> list){
	for(int i = 0; i < list.size(); i++){
		System.out.println(list.get(i));
	}
}

型別萬用字元初步?

//這樣沒問題的!下面的?是型別萬用字元,可以匹配任何型別。
public void test(List<?> list){
	for(int i = 0; i < list.size(); i++){
		System.out.println(list.get(i));
	}
}

設定型別萬用字元的上界

//必須是A型別及A的子類型別
List<? extends A>
import java.util.ArrayList;

class A{
	public void fun(){
		System.out.println("fun_A");
	};
}

class B extends A{
	public void fun(){
		System.out.println("fun_B");
	}
}

class C extends A{
	public void fun(){
		System.out.println("fun_C");
	}
}

public class Hello{
	//指定上界的萬用字元,可以傳入A型別及A型別的子類
	public void fun(ArrayList<? extends A> list){
		for(A a : list){
			a.fun();
		}
		//注意,這裡不得往list裡面新增B或C,因為list的型別不確定
		//list.add(new B());
	}
	public static void main(String[] args){
		ArrayList<A> list = new ArrayList<>();
		list.add(new C());
		list.add(new B());
		list.add(new A());
		
		new Hello().fun(list);
	}
}

設定型別萬用字元的下界

//必須是A型別及A的父類型別(super:超過)
List<? super A>
import java.util.ArrayList;
import java.util.Collection;


public class Hello{
	//把src集合的元素複製到dest集合。這就要求src集合和dest集合元素型別相同或者
	//src集合元素是dest集合元素的子類
	public static <T> T copy(Collection<? super T> dest, Collection<T> src){
		T last = null;
		for(T ele : src){
			last = ele;
			dest.add(ele);
		}
		return last;
	}
	
	public static void main(String[] args){
		//Integer extends Number
		ArrayList<Number> aln = new ArrayList<>();
		ArrayList<Integer> ali = new ArrayList<>();
		ali.add(6);
		Integer last = copy(aln, ali);
		System.out.println(last);

	}
}

設定萬用字元下界的具體案例可以檢視官方APITreeSet,TreeMap的帶有Comparator引數的構造器。具體示例程式碼見355頁~356頁。

泛型方法

筆者認為書上所說的泛型方法和型別萬用字元兩種方法實現泛型,其實如出一轍,無需區別,本質相同。抓住關鍵一點,子類可以複製給父類的引用變數。

import java.util.ArrayList;
import java.util.Collection;


public class Hello{
	public static <T> void fromArrayToCollection(T[] a, Collection<T> c){
		for(T o : a){
			c.add(o);
		}
	}
	
	public static void main(String[] args){
		String s[] = new String[10];
		Collection<String> cs = new ArrayList<>();
		fromArrayToCollection(s, cs);

		Integer[] i = new Integer[10];
		Collection<Number> cn = new ArrayList<>();
		fromArrayToCollection(i, cn);
	}
}

此程式中筆者看到了一個小小的發現:

Number n = new Number();//顯然是錯的,抽象類不可例項化
//這是正確的,因為右邊返回的是一個指向長度為12的Number引用型別的引用變數
Number[] n = new Number[12];

Java 7的“菱形”語法與泛型構造器


class A{

	public <T> A(T t){
		System.out.println(t);
	}
}

public class Hello{

	public static void main(String[] args){
		//根據傳入的引數推斷泛型型別
		new A(3);//T:Integer
		//根據傳入的引數推斷泛型型別
		new A("hello");//T:String
		
		new <String> A("hihihi");
		//new <String> A(4);//錯誤程式碼
	}
}

泛型方法與方法過載

一個類中定義了下面兩個方法(可以完成同一個操作):

public static <T> void copy(Collection<T>dest, Collection<? extends T>src){};
public static <T> void copy(Collection<? super T>dest, Collection<T>src){};

編譯報錯,不符合過載條件。

轉換與擦除

import java.util.ArrayList;
import java.util.List;


public class Hello{
	
	
	public static void main(String[] args){
		
		List<Integer> list = new ArrayList<>();
		list.add(1);
		list.add(3);
		//所謂的擦除
		List li = list;//把帶有泛型的變數賦給沒有泛型的,泛型資訊丟失
			//像List沒有指定泛型,稱為raw type,預設是宣告該泛型形參指定的第一上限型別
			//List沒指定泛型的話,預設就是Object
		
		//如果這裡寫成List<String>的話,再輸出ls.get(0)就會拋異常,整數不能自動轉為String
		List<Integer> ls = li;//沒泛型的變數賦給有泛型的變數,警告資訊-Xlint:unchecked
		System.out.println(ls.get(0));//執行正確
		
	}
}

泛型與陣列

關於陣列元素型別不能包含泛型變數或泛型形參(除非是無上限型別萬用字元:<?>)的內容見該書364~365頁。