建議先閱讀我前面分享過的《再談Java泛型---上》。java
類型通配符後端
先來看一段代碼:數組
private void test(List list) { for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } }
這段代碼沒毛病,只是編譯的時候會出現泛型警告,因而想到一個方案:app
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);
編譯會報錯:ide
這代表List<String>和List<Object>沒什麼關係,並非不少人想象的他們之間存在父子關係。this
注意spa
若是Man是Person的一個子類型,而G是具備泛型聲明的類或接口,那麼G<Man>並非G<Person>的子類型!!!設計
1orm
使用通配符
將上面的例子改爲通配符:
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。
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); } }
能夠看得出編譯不經過,由此證實上面的說法是正確的。
關於通配符的使用,在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改進的類型推斷
改進了泛型方法的類型推斷能力,類型推斷主要有兩個方面:
能夠經過調用方法的上下文來推斷泛型的目標類型。
可在方法調用鏈中,將推斷獲得的泛型類型傳遞到最後一個方法。
到此,本次Java泛型已經分享完畢。
萬丈高樓從地起!!! 加油