天天看點

Java 泛型 泛型的限制與局限性

不能用基本類型執行個體化類型參數

不能用類型參數代替基本類型:例如,沒有Pair,隻有Pair,其原因是類型擦除。擦除之後,Pair類含有Object類型的域,而Object不能存儲double值。這展現了Java語言中基本類型的獨立狀态。

運作時類型查詢隻适用于原始類型(raw type)

運作時:通常指在Classloader裝載之後,JVM執行之時

類型查詢:instanceof、getClass、強制類型轉換

原始類型:即(raw type),泛型類型經編譯器類型擦除後是Object或泛型參數的限定類型(例如Pair,Comparable就是T的限定類型,轉化後泛型的原始類型就是Comparable,是以Pair類不帶泛型是Pair),即Pair類含有Comparable類型的域

JVM中沒有泛型

if(a instanceof Pair<String>) //ERROR,僅測試了a是否是任意類型的一個Pair,會看到編譯器ERROR警告


if(a instanceof Pair<T>) //ERROR


Pair<String> p = (Pair<String>) a;//WARNING,僅測試a是否是一個Pair


Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) 
 //會得到true,因為兩次調用getClass都将傳回Pair.class
 //加入Java開發交流君樣:756584822一起吹水聊天
      

不能建立參數化類型的數組(泛型數組)

參數化類型的數組:指類型帶有泛型參數的數組,也即泛型數組,如Pair[] 、 T[]

不能執行個體化參數化類型的數組,例如:

Pair<String> table = new Pair<String>[10]; //ERROR
      

在這裡我們假設可以執行個體化,那麼經編譯器類型擦除後,table的類型是Pair[],我們再讓它協變為Object[]:

Object[] objArray = table;
      

而一般來說,數組會記住他的元素類型Pair,我們如果試圖存儲其他類型的元素,就會抛出異常(數組存儲檢查),例如:

objArray[0] = "Hello"; //ERROR--component type is Pair
      

但是,對于泛型類型Pair,類型擦除會使這種不同類檢查機制無效,這就是不能執行個體化泛型數組的原因!

objArray[0] = new Pair<Employee>();  
//如果泛型機制允許我們執行個體化數組,那麼這一步就沒理由出錯了!
//而這違背了我們的初衷(限定類型)
      

數組存儲隻會檢查擦除後的類型,又因為Java語言設計數組可以協變,是以可以通過編譯

能夠通過數組存儲檢查,不過仍會導緻一個類型錯誤,故不允許建立參數化類型的數組

注意,聲明類型為Pair[]的變量是合法的,隻是不能建立這些執行個體(我們應該直接用new Pair[10]{…}來初始化這個變量)

泛型數組的間接實作:

通過泛型數組包裝器,如ArrayList類,維護一個Object數組,然後通過進出口方法set、get來限定類型和強制轉換數組類型,進而間接實作泛型數組,

例如:ArrayList: ArrayList<Pair<T>>、ArrayList<T>
      

不能執行個體化類型變量T

即不能使用​

​new T(..) , new T[..] 或 T.class​

​這樣的表達式中的類型變量

例如: ​

​public Pair() { first = new T(); } //ERROR!​

​類型擦除将T改變成Object,調用非本意的new Object()

不能使用​

​new T(..)​

但是,可通過反射調用​

​Class.newInstance​

​方法來構造泛型對象(要注意表達式T.class是非法的)

public static <T> Pair<T> makePair(Class<T> cl){
    try{ return new Pair<>(cl.newInstance() , cl.newInstance()); }
    catch(Exception ex) { return null; }
}
//加入Java開發交流君樣:756584822一起吹水聊天
//這個方法可以按照下列方式調用:
Pair<String> p = Pair.makePair(String.class);
      

注意:Class類本身是泛型。String.class是一個Class的執行個體,是以makePair方法能夠推斷出pair的類型

不能使用new T[…]

解決方案:使用泛型數組包裝器,例如​

​ArrayList​

然而,當在設計一個泛型數組包裝器時,例如方法minmax傳回一個T[]數組,則泛型數組包裝器無法施展,因為類型擦除,​

​return (T [])new Object​

​是沒有意義的強轉不了。此時隻好利用反射,調用​

​Array.newInstance​

​:

import java.lang.reflect.*;
...
public static <T extends Comparable> T[] minmax(T... a){
    T[] mm = (T[]) Array.newInstance(a.getClass().getComponentType() , 2);
...
}
      

【API文檔描述】public Class<?> getComponentType() 傳回表示數組元件類型的 Class。如果此類不表示數組類,則此方法傳回 null。

而ArrayList類中的toArray方法的實作就麻煩了

public Object[] toArray() 無參,傳回Object[]數組即可 
public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
      

【API文檔描述】public static T[] copyOf(T[] original,int newLength)

  複制指定的數組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對于在原數組和副本中都有效的所有索引,這兩個數組将包含相同的值。對于在副本中有效而在原數組無效的所有索引,副本将包含 null。當且僅當指定長度大于原數組的長度時,這些索引存在。所得數組和原數組屬于完全相同的類。

public T[] toArray(T[] a) a - 要存儲清單元素的T[]數組(如果它足夠大)否則配置設定一個具有相同運作時類型的新數組,傳回該T[]數組

@SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //a.getClass()得運作時目的數組的運作時類型//加入Java開發交流君樣:756584822一起吹水聊天
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
      

【API文檔描述】

​public static <T,U> T[] copyOf(U[] original,int newLength, Class<? extends T[]> newType)​

複制指定的數組,截取或用 null 填充(如有必要),以使副本具有指定的長度。對于在原數組和副本中都有效的所有索引,這兩個數組将包含相同的值。對于在副本中有效而在原數組無效的所有索引,副本将包含 null。當且僅當指定長度大于原數組的長度時,這些索引存在。所得數組屬于 newType 類。

泛型類的靜态上下文中類型變量無效

泛型類不能在靜态域或靜态方法中引用類型變量

public class Singleton<T>{
    private static T singleInstance; //ERROR
    public static T getSingleInstance(){...} //ERROR
}
      

類型擦除後隻剩下Singleton類,因為靜态是以他隻包含一個singleInstance域,如果能運作則以Singleton類為模闆生成不同類型的域,是以産生了沖突

不能throws或catch泛型類的執行個體(有關異常)

泛型類繼承Throwable類不合法,如​

​public class Problem<T> extends Exception {...}​

​//ERROR 不能通過編譯

​catch​

​子句不能使用類型變量

public static <T extends Throwable> void doWork(Class<T> t){
    try{
            do work
        }catch (T e){ // ERROR
            Logger.global.info(...)
        }
}
      

不過,在異正常範中使用類型變量是允許的:

public static <T extends Throwable> void doWork(T t) throws T { //此時可以throws T
    try{//加入Java開發交流君樣:756584822一起吹水聊天
            do work
        }catch (Throwable realCause){ //捕獲到具體執行個體
            t.initCause(realCause); 
            throw t; //這時候抛具體執行個體,是以throw t 和 throws T 是可以的!
        }
}
      

此特性作用:可以利用泛型類、類型擦除、SuppressWarnings标注,來消除對已檢查(checked)異常的檢查,

unchecked和checked異常: Java語言規範将派生于Error類或RuntimeException的所有異常稱為未檢查(unchecked)異常,其他的是已檢查(checked)異常

Java異常處理原則:必須為所有已檢查(checked)異常提供一個處理器,即一對一個,多對多個

@SuppressWarnings("unchecked") 
   //SuppressWarning标注很關鍵,使得編譯器認為T是unchecked異常進而不強迫為每一個異常提供處理器
public static <T extends Throwable> void throwAs(Throwable e) throws
   T{  //因為泛型和類型擦除,可以傳遞任意checked異常,例如RuntimeException類異常

    throw (T) e;
}
      

假設該方法放在類Block中,如果調用 Block.throwAs(t); 編譯器就會認為t是一個未檢查的異常

public abstract class Block{
    public abstract void body() throws Exception;
    public Thread toThread(){
        return new Thread(){
                        public void run(){
                            try{
                                 body();
                            }catch(Throwable t){
                                 Block.<RuntimeException>throwAs(t);
                            }//加入Java開發交流君樣:756584822一起吹水聊天
                        }
                    };
    }

    @SuppressWarnings("unchecked")
    public static <T extends Throwable> void throwAs(Throwable e) throws T{
    throw (T) e ;
    }
}
      

再寫個測試類

public class Test{
    public static void main(String[] args){
        new Block(){
            public void body() throws Exception{
                //不存在ixenos檔案将産生IOException,checked異常!
                Scanner in = new Scanner(new File("ixenos"));
                while(in.hasNext())
                    System.out.println(in.next());
            }//加入Java開發交流君樣:756584822一起吹水聊天
        }.toThread().start();
    }
}
      

啟動線程後,throwAs方法将捕獲線程run方法所有checked異常,“處理”成unchecked

Exception(其實隻是騙了編譯器)後抛出;

有什麼意義?正常情況下,因為run()方法聲明為不抛出任何checked異常,是以必須捕獲所有checked異常并“包裝”到未檢查的異常中;意義:而我們這樣處理後,就不必去捕獲所有并包裝到​

​unchecked​

​異常中,我們隻是抛出異常并“哄騙”了編譯器而已

注意擦除後的沖突

Java泛型規範有個原則:“要想支援擦除的轉換,就需要強行限制一個泛型類或類型變量T不能同時成為兩個接口類型的子類,而這兩個接口是統一接口的不同參數化”

注意:非泛型類可以同時實作同一接口,畢竟沒有泛型,很好處理

class Calender implements Comparable<Calender>{...}

class GGCalender extends Calender implements Comparable<GGCalender>{...} //ERROR
      

在這裡GGCalender類會同時實作Comparable 和 Comparable,這是同一接口的不同參數化

更多内容詳見微信公衆号:Python測試和開發

Java 泛型 泛型的限制與局限性

繼續閱讀