java基礎鞏固-泛型基礎知識整理

標籤: javajava


[TOC]c++


本文對泛型的基本知識進行較爲全面的總結,並附上簡短的代碼實例,加深記憶。git

泛型

將集合中的元素限定爲一個特定的類型。github

術語

  • ArrayList<E> -- 泛型類型數組

  • ArrayList -- 原始類型app

  • E -- 類型參數函數

  • <> -- 讀做"typeof"測試

  • ArrayList<Integer> -- 參數化的類型spa

  • Integer -- 實際類型參數debug

幾點注意:

  • 參數化類型和原始類型相互兼容

ArrayList  collection1 = new ArrayList<Integer>();//經過,無warning
ArrayList<Integer> collection2 = new ArrayList();//經過,有warning
  • 參數化類型不考慮類型參數的繼承關係

ArrayList<String> collection3 = new ArrayList<Object>();//編譯不經過
ArrayList<Object> collection4 = new ArrayList<String>();//編譯不經過

可是

ArrayList collection5 = new ArrayList<Integer>();
ArrayList<String> collection6 = collection5;//編譯經過

"?"通配符

"?"表示任意類型,使用"?"通配符能夠引用各類參數化的類型,能夠調用與參數化無關的方法(如size()方法),不能調用與參數化有關的方法(如add()方法)

通配符的擴展

  • 限定通配符的上邊界

ArrayList<? extends Number > collection1= new ArrayList<Integer >();//編譯經過
ArrayList<? extends Number > collection2= new ArrayList<String>();//編譯不經過
  • 限定通配符的下邊界

ArrayList<? super Integer > collection3= new ArrayList<Number >();//編譯經過
ArrayList<? super Integer > collection4= new ArrayList<String>();//編譯不經過

自定義泛型方法

C++模板函數

template <class T> T add(T x, T y){
    return (T)(x+y);
}

而java的泛型基本上徹底在編譯器中實現,用於編譯器執行類型檢查和類型判斷,而後生成普通的非泛型的字節碼,這種實現技術爲「擦除」(erasure)。

"擦除"實例

泛型是提供給javac編譯器使用的,限定集合的輸入類型,編譯器編譯帶類型說明的集合時會去掉「類型」信息。

public class GenericTest {
    public static void main(String[] args) {
        new GenericTest().testType();
    }

    public void testType(){
        ArrayList<Integer> collection1 = new ArrayList<Integer>();
        ArrayList<String> collection2= new ArrayList<String>();
        
        System.out.println(collection1.getClass()==collection2.getClass());
        //二者class類型同樣,即字節碼一致
        
        System.out.println(collection2.getClass().getName());
        //class均爲java.util.ArrayList,並沒有實際類型參數信息
    }
}

輸出

true
java.util.ArrayList

使用反射可跳過編譯器,往某個泛型集合加入其它類型數據。

只有引用類型才能做爲泛型方法的實際參數
例子:

public class GenericTest {
    public static void main(String[] args) {
        swap(new String[]{"111","222"},0,1);//編譯經過
        
        //swap(new int[]{1,2},0,1);
        //編譯不經過,由於int不是引用類型
        
        swap(new Integer[]{1,2},0,1);//編譯經過
    }
    
    /*交換數組a 的第i個和第j個元素*/
    public static <T> void swap(T[]a,int i,int j){
        T temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

但注意基本類型有時能夠做爲實參,由於有自動裝箱拆箱
例子(編譯經過了):

public class GenericTest {
    public static void main(String[] args) {
        new GenericTest().testType();
        int a = biggerOne(3,5);
        //int 和 double,取交爲Number
        Number b = biggerOne(3,5.5);
        //String和int 取交爲Object
        Object c = biggerOne("1",2);
    }
    //從x,y中返回y
    public static <T> T biggerOne(T x,T y){
        return y;
    }
}

同時,該例還代表,當實參不一致時,T取交集,即第一個共同的父類。
另外,若是用Number b = biggerOne(3,5.5);改成String c = biggerOne(3,5.5);則編譯報錯:

Error:(17, 29) java: 不兼容的類型: 推斷類型不符合上限
    推斷: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>
    上限: java.lang.String,java.lang.Object

可是有一點沒搞清楚,我在IDEA裏面單步調試,發現結果以下圖:
泛型調試截圖-1
不知道b爲何是Double類型的(但直接Double b接收返回值會編譯報錯)。不知道跟IDE有沒有關係,是否是IDE在debug時會顯示這個對象最精確的類型?

類型參數的類型推斷

編譯器判斷泛型方法的實際類型參數的過程稱爲類型推斷。

  • 當某個類型變量只在整個參數列表的全部參數和返回值中的一處被應用了,那麼根據調用方法時該處的實際應用類型來肯定。即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型。
    例如:

swap(new String[3],1,2) -> static <E> void swap(E[]a,int i,int j)

  • 當某個類型變量在整個參數列表的全部參數和返回值中的多處被應用了,若是調用方法時這麼多處的實際應用類型都 對應同一種類型,則泛型參數的類型就是該類型。
    例如:

add(3,5) -> static <T> T add(T a,T b)

  • 當某個類型變量在整個參數列表的全部參數和返回值中的多處被應用了,若是調用方法時這麼多處的實際應用類型 對應不一樣的類型,且沒有返回值*,則取多個參數中的最大交集類型,即第一個公共父類。
    例如:

fill(new Integer[3],3.5) -> static <T> void fill(T a[],T v)

該例子實際對應的類型就是Number,編譯經過,運行出問題。

  • 當某個類型變量在整個參數列表的全部參數和返回值中的多處被應用了,若是調用方法時這麼多處的實際應用類型對應不一樣的類型,且使用有返回值,則優先考慮返回值的類型

例如:

int x = add(3,3.5) -> static <T> T add(T a,T b)

上例編譯報錯,x類型改成float也報錯,改成Number成功。

  • 參數類型的類型推斷具備傳遞性

例子:

copy(new Integer[5],new String[5]) -> static <T> void copy(T []a,T []b)

該例推斷實際參數類型爲Object,編譯經過.

copy(new ArrayList<String>,new Integer[5]) -> static <T> void copy(Collection<T>a,T[]b)

該例則根據參數化的ArrayList類實例將類型變量直接肯定爲String類型,編譯報錯。


自定義泛型類

例子

public class GenericDao<T>{
    public void add(T x){
    }
    
    public T findById(int id){
        return null;
    }
    
    public void delete(T obj){
    }
    
    public void delete(int id){
    }
    
    public void update(T obj){
    }
    
    public T findByUserName(String name){
        return null;
    }

    public <T> Set<T> findByConditions(String where){
        return null;
    }
    
}

注意:當一個變量被聲明爲泛型時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用。由於靜態成員是被所參數化的類所共享的,因此靜態成員不該該有類級別的類型參數

泛型方法和泛型類的比較

例子:

public class A<T>(){
    //泛型類的成員方法,該T受A後面的T的限制
    public T memberFunc(){
        return null;
    }
    //泛型方法,這裏的T和和類A的T是不一樣的
    public static <T> T genericFunc(T a){
        return null;
    }
    public static void main(String[] args) {
        //編譯不經過
        //Integer i = A<String>().findByUserName("s");
        
        //編譯經過
        Set<Integer> set=  A<String>().findByConditions("s");
    }
}

這裏Integer i = A<String>().findByUserName("s");會編譯報錯:

Error:(35, 61) java: 不兼容的類型: java.lang.String沒法轉換爲java.lang.Integer

由這個例子可知,泛型方法的T和和類A的T是不一樣的。


泛型和反射

經過反射得到泛型的實際類型參數

把泛型變量當成方法的參數,利用Method類的getGenericParameterTypes方法來獲取泛型的實際類型參數
例子:

public class GenericTest {
    public static void main(String[] args) throws Exception {
        getParamType();
    }
    
     /*利用反射獲取方法參數的實際參數類型*/
    public static void getParamType() throws NoSuchMethodException{
        Method method = GenericTest.class.getMethod("applyMap",Map.class);
        //獲取方法的泛型參數的類型
        Type[] types = method.getGenericParameterTypes();
        System.out.println(types[0]);
        //參數化的類型
        ParameterizedType pType  = (ParameterizedType)types[0];
        //原始類型
        System.out.println(pType.getRawType());
        //實際類型參數
        System.out.println(pType.getActualTypeArguments()[0]);
        System.out.println(pType.getActualTypeArguments()[1]);
    }

    /*供測試參數類型的方法*/
    public static void applyMap(Map<Integer,String> map){

    }

}

輸出結果:

java.util.Map<java.lang.Integer, java.lang.String>
interface java.util.Map
class java.lang.Integer
class java.lang.String

做者更多文章:@brianway

相關文章
相關標籤/搜索