天天看點

java基礎鞏固-詳解泛型

java泛型(generics)為jdk5引入的新特性,泛型提供了編譯時類型安全檢測機制,可以在編譯時檢測到非法的類型。

泛型的本質是參數化類型,也就是說所操作的資料類型被指定為一個參數。

使用泛型的好處

它的主要目标是保障java的類型安全,簡化程式設計,泛型可以使編譯器知道一個對象限定類型是什麼,所有的強制轉換都為自動和隐式的。

舉個簡單的栗子
public class test1 {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("字元串");
        list.add(1);
        for (Object a : list) {
            System.out.println("toString轉換->" + a.toString());
            System.out.println("強轉->" + (String)a);
        }
    }
}
           
執行結果
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
toString轉換字元串
強轉字元串
toString轉換1
    at generics.test1.main(test1.java:13)
           

雖然編譯的時候沒有報錯,但是運作的時候強轉為String的時候出現類型轉換的錯,可以看出來這種寫法是不安全的。

進一步做點改良(使用泛型後,編譯器報錯,這樣可以預防一些後續編譯通過但運作報錯的情況)
java基礎鞏固-詳解泛型
泛型的一個重要特性:跟反射不同的是,泛型檢查隻在編譯期間有效,而反射則是在運作時有效。

下面做個簡單的測試

public class test2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //測試泛型
        List<String> list1 = new ArrayList<String>();
        List<Integer> list2 = new ArrayList<Integer>();
        Class class1 = list1.getClass();
        Class class2 = list2.getClass();
        if(class1.equals(class2)) {
            System.out.println("類型相同");
        }
      
        //測試反射
        Map<String, String> map = new HashMap<String, String>();
        String key = "key";
        Integer val = 1;
        //通過反射擷取方法
        Method m = HashMap.class.getDeclaredMethod("put", new Class[] { Object.class, Object.class });
        //invoke意在将方法參數化 動态調用Method類代表的方法 并傳入參數
        m.invoke(map, key, val);
        System.out.println(map);
        System.out.println(map.get(key)); //擷取key為key的值
    }
}
           
java基礎鞏固-詳解泛型

image.png

泛型的使用方式

1.泛型類
2.泛型接口
3.泛型方法
           

泛型類

//T,E,K,V等均表示為泛型
public class Generic<T> {
    private T key;//成員變量類型為T,由外部指定

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的傳回值類型為T,T的類型由外部指定
        return key;
    }

    public static void main(String[] args) {
        //在執行個體化泛型類時 需指定T的具體類型(這邊5就代表integer) 
        Generic<Integer> s1 = new Generic<Integer>(5);
        Generic<String> s2 = new Generic<String>("5");

        System.out.println(s1.getClass());//class generics.Generic
        System.out.println(s2.getClass());//class generics.Generic

        System.out.println(s1.getKey().getClass()); //class java.lang.Integer
        System.out.println(s2.getKey().getClass()); //class java.lang.String
      
        /**
         *     定義泛型類不一定要傳入泛型類型實參,如果傳入的化會在編譯時做限制,不傳的化可以為任一類型,
         *     但處于安全考慮一般都定義否則一些場景容易出現ClassCastException錯誤
         */
        Generic t1 = new Generic(5);
        Generic t2 = new Generic(0.5);
        System.out.println(t1.getKey()); //5
        System.out.println(t2.getKey()); //0.5
        System.out.println(t1.getKey().getClass()); //class java.lang.Integer
        System.out.println(t2.getKey().getClass()); //class java.lang.Double
    }
    }
}
           

泛型接口

泛型接口與類定義和使用大緻相同,例如Map接口的一小段代碼

public interface Map<K,V> {
  ...
  V put(K key, V value);
  void putAll(Map<? extends K, ? extends V> m);//指明泛型的上邊界
  ...  
}
           

實作泛型接口,假設實作map

//假如一個類實作了泛型接口,需要将泛型聲明(test3<K,V>) 一起加到類中 否則編譯報錯 
public class test3<K,V> implements Map<K,V>{
    @Override
    public int size() {
        return 0;
    }

    @Override
    public boolean isEmpty() {
        return false;
    }
    ...
}
           

泛型方法

泛型類,是在執行個體化類的時候指明【泛型】的具體類型;泛型方法,是在調用方法的時候指明泛型的具體類型 。

這樣說有點繞口 舉兩個栗子

/**
    泛型類
**/
//定義泛型類 該類
public class Test<T> {
    public T key;
    public test5(T key) {
        this.key = key;
    };
}
//通過執行個體化指明【泛型】T的類型為String 
Test<String> t = new Test<Sring>("123");
           
/**
    泛型方法 
    定義泛型方法時 必須在傳回值前面加一個 <T> 來聲明這是一個泛型方法
**/
public class test5<T> {
    //在這裡<T>表示傳回的類型是T的 該方法的作用是建立任一指定類型并傳回
    public <T> T getObject(Class<T> c) throws IllegalAccessException, InstantiationException {
        T t = c.newInstance();//建立泛型對象
        return t;
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        test5 t = new test5();
        //調用泛型方法【在調用方法的時指明泛型具體類型 這邊用User型】
        //在這object就是User的執行個體 這邊測試别用類似java.lang.xxx這樣BootStrap類加載器加載的類 否則輸出為空
        Object object = t.getObject(Class.forName("generics.User"));
        System.out.println(object.getClass()); //輸出class generics.User
    }
}
           

泛型的上下邊界

使用泛型的時,可以通過傳入泛型類型實參進行上下邊界的限制。

如定義一個泛型類

public class Generic<T extends Number> { //此處為上邊界 意為給定T類型需為Number子類 T super XX 為下邊界
    private T key;
    public Generic(T key) {
        this.key = key;
    }
    public T getKey(){
        return key;
    }
}
           

如果這樣執行個體化Generic類編譯器會報錯,因為String不是Number的子類。

Generic<String> generic = new Generic<String>("11111");

一些思考

1.下面這個getKey是否為泛型方法
public class Generic<T> { 
    private T key;
    public Generic(T key) {
    this.key = key;
    }
    public T getKey(){
       return key;
    }
}
           

并不是,雖然在方法中使用了泛型,這個是類中普通的成員方法,因為它的傳回值是聲明泛型類【Generic】時已經聲明過的泛型【T】,是以在方法中可以繼續使用T這個泛型。

2.經常在使用泛型的代碼中看見通配符 【?】它的作用是什麼,和T的差別在哪呢

比如Map源碼

void putAll(Map<? extends K, ? extends V> m);
           

在這裡?是通配符,泛指所有類型,常用于不确定類型的情況

? extends T 指T類型或T的子類型

? super T 指T類型或T的父類型

它們的差別在于

"T"是定義類或方法時聲明的東西,"?"是調用時傳入的東西

①T常用于聲明一個泛型類或者泛型方法

②?常用于使用泛型類或泛型方法

聲明泛型類時不能用無界通配符<?>

//error example
class demo<?> { 
  private ? item; 
}
           
通配符是可以用來使用定義好的泛型的 但是T不能用來繼續使用已經定義好的泛型

當我們在外面使用一個帶泛型T的類或方法時,T應該用一個實際的資料類型替代它,也可以使用?通配符

public class User {
    HashMap<T,String> map = new HashMap<String, String>(); //error example
    HashMap<?,String> map = new HashMap<String, String>(); //right example
}
           
看hashMap源碼中的putAll方法
java基礎鞏固-詳解泛型
簡單來說,T一般是聲明時用(泛型類,泛型方法),而?通配符一般是使用時用,可以使用作為通用類。