再談Java泛型---下

語言: CN / TW / HK

建議先閱讀我前面分享過的《再談Java泛型---上》。

型別萬用字元

先來看一段程式碼:

private void test(List list) {	for (int i = 0; i < list.size(); i++) {	System.out.println(list.get(i));	}	}

這段程式碼沒毛病,只是編譯的時候會出現泛型警告,於是想到一個方案:

private void test(List<Object> list) {	for (int i = 0; i < list.size(); i++) {	System.out.println(list.get(i));	}	}

表面上看起來沒什麼問題,這個方法聲明確實沒有任何問題,但問題是呼叫該方法傳入的實際引數時可能不是我們所期望的,例如:

 List<String> strings=new ArrayList<>();	new LessJDK5Demo().test(strings);

編譯會報錯:

640?wx_fmt=png

這表明List<String>和List<Object>沒什麼關係,並不是很多人想象的他們之間存在父子關係。

注意

如果Man是Person的一個子型別,而G是具有泛型宣告的類或介面,那麼G<Man>並不是G<Person>的子型別!!!

1

使用萬用字元

將上面的例子改成萬用字元:

private void test(List<?> list) {	for (int i = 0; i < list.size(); i++) {	System.out.println(list.get(i));	}	}

這個問號(?)被稱為萬用字元,它的元素型別可以匹配任何型別。但是得小心這個匹配任何型別,以下程式碼就編譯通不過:

List<?> strings=new ArrayList<String>();	strings.add(new Object())

因為程式無法確定strings集合中元素的型別,所以不能新增物件。

2

設定萬用字元上限

public class Person {	private int info;	public Person(int info) {	this.info = info;	}	}
public class Sub extends Person {	public Sub(int info) {	super(info);	}	}
private static void test(List<Person> peoples){	peoples.forEach(person -> {	System.out.println(person);	});	}

因為List<Sub>不是List<Person>的子類,所以以下程式碼是編譯通不過的:

List<Sub> subs=new ArrayList<>();	test(subs);//這一行編譯通不過

為了能讓上面編譯通過,有一種辦法,那就是萬用字元的上限,改造test方法入參:

private static void test(List<? extends Person> peoples){	peoples.forEach(person -> {	System.out.println(person);	});	}

由此可一推斷出,對於類似List<?>這樣的萬用字元而言,期上限就是Object。

對於指定萬用字元上限的泛型,相當於萬用字元上限是Object。

640?wx_fmt=png

3

萬用字元的下限

萬用字元的下限與萬用字元的上限是相反的,格式如下:

private static void test(List<? super Sub> peoples) {	peoples.forEach(person -> {	System.out.println(person);	});	}

採用<? super 下限型別>

List<Person> subs = new ArrayList<>();	test(subs);//編譯通過	List<Object> subs = new ArrayList<>();	test(subs);//編譯通過

可以頂一個Sub的子類的證明:

public class SubSub extends Sub {	public SubSub(int info) {	super(info);	}	}

640?wx_fmt=png

可以看得出編譯不通過,由此證明上面的說法是正確的。

關於萬用字元的使用,在Java集合框架中也有使用到:java.util.TreeMap中

public class TreeMap<K,V>{	//下限萬用字元	private final Comparator<? super K> comparator;	//下限萬用字元	public TreeMap(Comparator<? super K> comparator) {	this.comparator = comparator;	}	  //上限萬用字元	public TreeMap(Map<? extends K, ? extends V> m) {	comparator = null;	putAll(m);	  }	  //....其他程式碼就不貼了	}

4

設定泛型形參的上限

程式碼:

public class Apple<T extends Number> {	T t;	}
Apple<Integer> apple=new Apple<>();	Apple<Double> apple1=new Apple<>();	//下面這一行編譯不通過	Apple<String> str=new Apple<>();

java泛型不僅允許在使用萬用字元形參時設定上限,而且可以在定義泛型形參時設定上限,用於表示傳給該泛型形參的實際型別,要麼是該上限型別,要麼是該上限型別的子類。

泛型方法

1

定義泛型方法

假設需要實現這一一個方法:該方法負責將一個Object陣列的所有元素新增到一個Colletion集合中,考慮採用如下程式碼來實現:

private  static void fromArrayToColletion(Object [] os, Collection<Object> cs){	     for (Object object:os) {	cs.add(object);	}	}

上面方法沒問題,但是考慮到如果使用String[] 和List<String>作為入參就會編譯報錯。這裡顯然不能用萬用字元Colletion<?>,因為Java不允許把物件放進一個未知型別的集合中。

為了解決上述問題,java1.5版本提供了泛型方法,所謂泛型方法就是在宣告方法時定義一個或多個泛型形參,格式如下:

修飾符 <T, S> 返回值型別  方法名(形參列表){	 //方法體.....	}

修正上面的例子:

private static <T> void fromArrayToColletion(T[] objects,	Collection<T> collection) {	for (T object : objects) {	collection.add(object);	}	}

呼叫案例:

public static void main(String[] args) {	String[] a = {"java後端技術棧", "大中華"};	List<String> la = new ArrayList<>();	fromArrayToColletion(a, la);		Integer[] b = {1, 9};	List<Integer> lb = new ArrayList<>();	fromArrayToColletion(b, lb);	}

為了不讓編譯器能準確地推斷出方形方法中泛型的型別,不要製造迷惑!系統一旦迷惑了,就是你錯了,看如下程式:

private static <T> void fromArrayToCollection(Collection<T>  from, Collection<T> to) {	for (T object : from) {	to.add(object);	}	}	public static void main(String[] args) {	List<String> l = new ArrayList<>();	List<Object> s = new ArrayList<>();	       //編譯報錯	fromArrayToCollection(l, s);	}

為了避免上述問題,我們可以改寫一下上面的方法:

private static <T> void fromArrayToCollection(Collection<? extends T>  from, Collection<T> to) {	for (T object : from) {	to.add(object);	}	}	public static void main(String[] args) {	List<String> l = new ArrayList<>();	List<Object> s = new ArrayList<>();	//正常編譯成功	fromArrayToCollection(l, s);	}

那麼到底是何時使用泛型方法?

何時使用型別萬用字元呢?

2

泛型方法和型別萬用字元的區別

大多數時候都可以使用泛型方法來代替型別萬用字元,例如:對於Java的Collection介面中兩個方法定義:

public interface Collection<E> {	boolean containsAll(Collection<?> c);	boolean addAll(Collection<? extends E> c);	}

上面的集合中的兩個方法的形參都採用了型別萬用字元,也可以使用泛型方法的形式。如:

public interface Collection<E> {	<T> boolean containsAll(Collection<?> c);	<T extends E> boolean addAll(Collection<T> c);	}

<T extends E>是泛型形式,這時定義泛型形參時設定了上限。

上面兩個方法中泛型形參T只是用了一次,泛型形參T產生的唯一效果是可以在不同的呼叫點傳入不同實際型別。對於這種情況,應該使用萬用字元;萬用字元就是被設計用來支援靈活的子類化的。泛型方法允許泛型形參被用來表示一個或多個引數之間的型別依賴關係,或者方法返回值與防範引數之間的依賴關係。如果沒有這樣的關係依賴型別,就不應該使用泛型方法。

型別萬用字元和泛型方法一個很明顯的區別:

型別萬用字元既可以在方法簽名中定義形參的型別,也可以用於定義變數的型別;但是泛型方法中的泛型形參必須在對應方法中顯示宣告。

3

JDK1.8改進的型別推斷

改進了泛型方法的型別推斷能力,型別推斷主要有兩個方面:

  1. 可以通過呼叫方法的上下文來推斷泛型的目標型別。

  2. 可在方法呼叫鏈中,將推斷得到的泛型型別傳遞到最後一個方法。

到此,本次Java泛型已經分享完畢。

萬丈高樓從地起!!!  加油


分享到: