Java中 的理解*


? 通配符類型

- <? extends T> 表示類型的上界,表示參數化類型的可能是T 或是 T的子類; <? super T> 表示類型下界(Java Core中叫超類型限定),表示參數化類型是此類型的超類型(父類型),直至Object;

上界<? extends T>不能往里存,只能往外取

比如,我們現在定義:List<? extends T>首先你很容易誤解它為繼承於T的所有類的集合,你可能認為,你定義的這個List可以用來put任何T的子類,那么我們看下面的代碼:

import java.util.LinkedList;
import java.util.List; public class test { public static void main(String[] args) { List<? extends Father> list = new LinkedList<>(); list.add(new Son()); } } class Human{ } class Father extends Human{ } class Son extends Father{ } class LeiFeng extends Father { }

list.add(new Son());這行會報錯:The method put(Son) is undefined for the type List<capture#1-of ? extends Father>

List<? extends Father> 表示 “具有任何從Father繼承類型的列表”,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象。可以添加null,因為null 可以表示任何類型。所以List 的add 方法不能添加任何有意義的元素,但是可以接受現有的子類型List 賦值。

你也許試圖這樣做:

List<? extends Father> list = new LinkedList<Son>(); list.add(new Son());

即使你指明了為Son類型,也不能用add方法添加一個Son對象。

list中為什么不能加入Father類和Father類的子類呢,我們來分析下。

List<? extends Father>表示上限是Father,下面這樣的賦值都是合法的

   List<? extends Father> list1 = new ArrayList<Father>(); List<? extends Father> list2 = new ArrayList<Son>(); List<? extends Father> list3 = new ArrayList<LeiFeng>();

如果List<? extends Father>支持add方法的話:

  • list1可以add Father和所有Father的子類;
  • list2可以add Son和所有Son的子類;
  • list3可以add LeiFeng和所有LeiFeng的子類。

下面代碼是編譯不通過的:

list1.add(new Father());//error list1.add(new Son());//error

原因是編譯器只知道容器內是Father或者它的派生類,但具體是什么類型不知道。可能是Father?可能是Son?也可能是LeiFeng,XiaoMing?編譯器在看到后面用Father賦值以后,集合里並沒有限定參數類型是“Father“。而是標上一個占位符:CAP#1,來表示捕獲一個Father或Father的子類,具體是什么類不知道,代號CAP#1。然后無論是想往里插入Son或者LeiFeng或者Father編譯器都不知道能不能和這個CAP#1匹配,所以就都不允許。

所以通配符<?>和類型參數的區別就在於,對編譯器來說所有的T都代表同一種類型。比如下面這個泛型方法里,三個T都指代同一個類型,要么都是String,要么都是Integer。

public <T> List<T> fill(T... t);

但通配符<?>沒有這種約束,List<?>單純的就表示:集合里放了一個東西,是什么我不知道。

所以這里的錯誤就在這里,List<? extends Father>里什么都放不進去。

List<? extends Father> list不能進行add,但是,這種形式還是很有用的,雖然不能使用add方法,但是可以在初始化的時候一個Season指定不同的類型。比如:

List<? extends Father> list1 = getFatherList();//getFatherList方法會返回一個Father的子類的list

另外,由於我們已經保證了List中保存的是Father類或者他的某一個子類,所以,可以用get方法直接獲得值:

List<? extends Father> list1 = new ArrayList<>(); Father father = list1.get(0);//讀取出來的東西只能存放在Father或它的基類里。 Object object = list1.get(0);//讀取出來的東西只能存放在Father或它的基類里。 Human human = list1.get(0);//讀取出來的東西只能存放在Father或它的基類里。 Son son = (Son)list1.get(0);

下界

//super只能添加Father和Father的子類,不能添加Father的父類,讀取出來的東西只能存放在Object類里 List<? super Father> list = new ArrayList<>(); list.add(new Father()); list.add(new Human());//compile error list.add(new Son()); Father person1 = list.get(0);//compile error Son son = list.get(0);//compile error Object object1 = list.get(0);

因為下界規定了元素的最小粒度的下限,實際上是放松了容器元素的類型控制。既然元素是Father的基類,那往里存粒度比Father小的都可以。出於對類型安全的考慮,我們可以加入Father對象或者其任何子類(如Son)對象,但由於編譯器並不知道List的內容究竟是Father的哪個超類,因此不允許加入特定的任何超類(如Human)。而當我們讀取的時候,編譯器在不知道是什么類型的情況下只能返回Object對象,因為Object是任何Java類的最終祖先類。但這樣的話,元素的類型信息就全部丟失了。

PECS原則

最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經很好理解了:

  • 頻繁往外讀取內容的,適合用上界Extends。
  • 經常往里插入的,適合用下界Super。

總結

  • extends 可用於返回類型限定,不能用於參數類型限定(換句話說:? extends xxx 只能用於方法返回類型限定,jdk能夠確定此類的最小繼承邊界為xxx,只要是這個類的父類都能接收,但是傳入參數無法確定具體類型,只能接受null的傳入)。
  • super 可用於參數類型限定,不能用於返回類型限定(換句話說:? supper xxx 只能用於方法傳參,因為jdk能夠確定傳入為xxx的子類,返回只能用Object類接收)。
  • ? 既不能用於方法參數傳入,也不能用於方法返回。


<? extends SomeClass>與<T extends SomeClass>的區別

看apache parquet源碼時,發現代碼各種泛型嵌套,有必要系統整理一下關於泛型的各種知識,在此做一總結。

首先是名詞對應表,不需要記住右邊的名字,但需要知道左邊的各種用法

List<String> —- 參數化的類型 
List<E> —- 泛型 
List<?> —- 無限制通配符類型 
<E extends SomeClass> —- 有限制類型參數 
List <? extends SomeClass>—- 有限制通配符類型 
<T extends Comparable<T>> —– 遞歸類型限制 
static <E> List<E> asList(E[] a) —- 泛型方法

下面自己的實驗包括代碼,標號1是解決題目里描述的問題,其余的標號也是自己遇到的一些關鍵的問題。

疑問&要解釋的東西


1:<E extends ClassA> 與 <? extends ClassA>有什么區別?

  • 答:當我第一次接觸這兩名詞時,感覺他們的功能是一樣的,T可以代表任意的子類,?也可以代表任意的子類。 
    首先我們明確一下兩邊的名字,限制類型 & 通配符類型,<E extends ClassA>表示后續都只能使用E進行某些判斷或操作,而<? extends ClassA>?表示后續使用時可以是任意的。 
    舉個<E extends ClassA>最常見的例子,用於比較操作,比如返回“最大值”,“最大值”的定義為:整型、浮點型返回最大值,字符串返回字典序最大者,由於想調用compareTo函數,我們讓所有參數都繼承Compareble,即T extends Comparable<T>,整個測試代碼如下
package test; /** * 定義了 <T extends someClass>, * 里面的代碼便只能用somClass的子類T進行比較或其他操作。 */ public class MaximumTest { // determines the largest of three Comparable objects public static <T extends Comparable<T>> T maximum(T x, T y, T z) { T max = x; // assume x is initially the largest if ( y.compareTo( max ) > 0 ) { max = y; // y is the largest so far } if ( z.compareTo( max ) > 0 ) { max = z; // z is the largest now } return max; // returns the largest object } public static void main(String args[]) { System.out.printf( "Max of %d, %d and %d is %d\n\n", 3, 4, 5, maximum( 3, 4, 5 ) ); System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n\n", 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) ); System.out.printf( "Max of %s, %s and %s is %s\n","pear", "apple", "orange", maximum( "pear", "apple", "orange" ) ); } }

注釋里已經寫清楚了,我們只能用T類型來進行一些操作,我們不能把T替換成?,因為?並不是一個類名,它只是一個通配符,然后舉個<? extends ClassA>的例子。

比如我們有一個Stack類,類里提供一個pullAll方法,我們想把一系列元素全部放到堆棧中,如下方法

   // Stack定義 public class Stack<E> { public Stack(); public E pop(); public boolean isEmpty(); } // ... public <E> void pushAll(Iterable<E> src) { for(E e : src) { push(e); } }

這個方法編譯時沒問題,Iterable src的元素類型與堆棧的類型完全匹配就沒有問題。但是假如有一個Stack<Number>調用了push(intVal),這里的intVal是Integer類型,這是可以的,因為Integer是Number的一個子類型,但下面的代碼會報編譯錯誤,

Stack<Number> numberStack = new Stack<Number>(); Iterable<Integer> integers = "..."; numberStack.pushAll(integers);

因為在Java中,參數化類型是不可變的。所以現在我們的通配符類型就派上用場了,代碼如下

public void pushAll(Iterable<? extends E> scr) { for( E e : src) { push(e); } } 

此處就必須用通配符?,代表泛型的泛指“E的某個子類型的Iterator接口”。 
擴張閱讀:Stackoverflow : 區別


2、List<Object> o = new ArrayList<Long>(); 報錯

  • 不同於數組Object[] o,Long[] o,因為List<Type1>List<Type2>不互為子類型or超類型

3、無法創建泛型數組。


4、泛型方法的使用

    • static <E> List<E> asList(E[] a) —- 泛型方法 
      參考這篇文章Java中的泛型方法的解釋,注意區分Class與實例。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM