文章標題

泛型與通配符詳解

1回顧泛型類
  泛型類:具有一個或多個泛型變量的類被稱之爲泛型類。

複製代碼
class ClassGenericity<T> {  
    //在類裏面可以直接使用T的類型
    T aa;
    public void test11(T bb) {
        //................
    }
    //靜態方法 在類上面定義的泛型,不能再靜態方法裏面使用
    public static <A> void test12(A cc) {
        //..................
    }
}
public class TestClassGenericity{
    public static void main(String[] args) {
        ClassGenericity<String>genericity=new ClassGenericity<String>();
        genericity.test11("1234");
        ClassGenericity.test12(6789);
    }   
}
複製代碼

2 泛型方法
  泛型方法的特點:
    方法的參數中可以使用泛型變量;
    方法的返回值中可以使用泛型變量。

複製代碼
public class MethodGenericity {
    public static void main(String[] args) {
        String arr[]={"aaa","bbb","ccc","ddd"};
        System.out.println(Arrays.toString(arr));
        exchange(arr,0,3);                //交換0,3位置元素
        System.out.println(Arrays.toString(arr));
    }
    private static<T> void exchange(T[] arr, int i, int j) {
        T temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}
複製代碼
複製代碼
public class Test1 {
    public <T> T get(T[] ts) {
        return ts[ts.length / 2];
    }
    @Test
    public void test()
    {
        String[] names ={"zhangSan", "liSi","wangWu"};
        System.out.println(get(names));//輸出lisi
    }
}
複製代碼

  調用泛型方法時無需指定泛型變量,編譯器會通過實際參數的類型來識別泛型變量的類型,上例中傳遞的參數爲String[]類型,那麼相當於給泛型類型T賦值爲String。

3 繼承(實現)泛型類(接口)
  繼承泛型類需要爲父類的泛型變量賦值!就好比創建泛型類的對象時需要給泛型變量賦值一樣。
  創建泛型類對象:List<String> list = new ArrayList<String>();
  繼承泛型類1(子類也是泛型類):public class MyList1<T> extends ArrayList<T> {…}
  繼承泛型類2(子類不是泛型類):public class MyList2 extends ArrayList<String> {…}

4 通配符
  爲了說明通配符的作用,我們先看個例子:
    List<Object> list1 = new ArrayList<String>();
    List<Object> list2 = new ArrayList<Integer>();

  上面的調用都是編譯不通過的!這說明想寫一個即可以打印list1,又可以打印list2的方法是不可能的!

public static void fun(List<Object> list) {…}
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);//編譯不通過
fun(list2);//編譯不通過

  如果把fun()方法的泛型參數去除,那麼就OK了。即不使用泛型!

public static void fun(List list) {…}//會有一個警告
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);
fun(list2);

  上面代碼是沒有錯了,但會有一個警告。警告的原因是你沒有使用泛型!Java希望大家都去使用泛型。你可能會說,這裏根本就不能使用泛型!!!!!

  4.1 通配符概述
    通配符就是專門處理這一問題的。

public static void fun(List<?> list) {…}

    上面代碼中的“?”就是一個通配符,它只能在“<>”中使用。這時你可以向fun()方法傳遞List<String>、List<Integer>類型的參數了。當傳遞List<String>類型的參數時,表示給“?”賦值爲String;當傳遞List<Integer>類型的參數給fun()方法時,表示給“?”賦值爲Integer。

  4.2 通配符的缺點
    帶有通配符的參數不能使用與泛型相關的方法,例如:list.add(“hello”)編譯不通過。
    上面的問題是處理了,但通配符也有它的缺點。在上面例子中,List<?> list參數中的通配符可以被賦任何值,但同時你也不知道通配符被賦了什麼值。當你不知道“?”是什麼時,會使你不能使用任何與泛型相關的方法。也就是說fun()方法的參數list不能再使用它的與泛型相關的方法了。例如:list.add(“hello”)是錯誤的,因爲List類的add()方法的參數是T類型,而現在你不知道T是什麼類型,你怎麼去添加String的東西給list呢?如果使用者在調用fun()方法時傳遞的不是List<String>,而是List<Integer>時,你添加String當然是不可以的。當然,還可以調用list的get()方法。就算你不知道“?”是什麼類型,但它肯定是Object類型的。所以你可以:Object o = list.get(0);

  4.3 通配符的限制
    通配符只能出現在引用的定義中,而不能出現在創建對象中。例如:new ArrayList<?>(),這是不可以的。ArrayList<?> list = null,這是可以的。

  4.4 帶有下邊界的通配符
    List<? extends Number> list;
      其中<? extends Number>表示通配符的下邊界,即“?”只能被賦值爲Number或其子類型。

public static void fun(List<? extends Number> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Double>());//ok
fun(new ArrayList<String>());//不ok

      當fun()方法的參數爲List<? extends Number>後,說明你只能賦值給“?”Number或Number的子類型。雖然這多了一個限制,但也有好處,因爲你可以用list的get()方法。就算你不知道“?”是什麼類型,但你知道它一定是Number或Number的子類型。所以:Number num = list.get(0)是可以的。但是,還是不能調用list.add()方法!

  4.5 帶有下邊界的通配符
    List<? super Integer> list;
      其中<? super Integer>表示通配符的下邊界,即“?”只能被賦值爲Integer或其父類型。

public static void fun(List<? super Integer> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Number>());//ok
fun(new ArrayList<Object>());//ok
fun(new ArrayList<String>());//不ok

      這時再去調用list.get()方法還是隻能使用Object類型來接收:Object o = list.get(0)。因爲你不知道“?”到底是Integer的哪個父類。但是你可以調用list.add()方法了,例如:list.add(new Integer(100))是正確的。因爲無論“?”是Integer、Number、Object,list.add(new Integer(100))都是正確的。

  4.6 通配符小結
    1. 方法參數帶有通配符會更加通用;
    2. 帶有通配符類型的對象,被限制了與泛型相關方法的使用;
    3. 下邊界通配符:可以使用返回值爲泛型變量的方法;
    4. 上邊界通配符:可以使用參數爲泛型變量的方法。

  4.7   通配符應用實例

  boolean addAll(Collection<? extends E> c)//JDK集合的addAll方法

List<Number> numList = new ArrayList<Number>();
List<Integer> intList = new ArrayList<Integer>();
numList.addAll(intList);//正確!!!!!!addAll(Collection<? extends Number> c), 傳遞的是List<Integer>

  如果用改成boolean addAll(Collection<E> c)

List<Number> numList = new ArrayList<Number>();
List<Integer> intList = new ArrayList<Integer>();
numList.addAll(intList);//錯誤!!!!!addAll(Collection<Number> c), 傳遞的是List<Integer>

5 通配符應用實例

複製代碼
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
    public void fun1() {
        Object[] objArray = new String[10];//正確!!!
        objArray[0] = new Integer(100);//錯誤!!!編譯器不會報錯,但是運行時會拋ArrayStoreException
        //List<Object> objList = new ArrayList<String>();//錯誤!!!編譯器報錯,泛型引用和創建兩端,給出的泛型變量必須相同!
    }


    public void fun2() {
        List<Integer> integerList = new ArrayList<Integer>();
        print(integerList);
        List<String> stringList = new ArrayList<String>();
        print(stringList);
    }
    /*
     * 其中的?就是通配符,?它表示一個不確定的類型,它的值會在調用時確定下來
     * 通配符只能出現在左邊!即不能在new時使用通配符!!!
     * List<?> list = new ArrayList<String>();
     * 通配符好處:可以使泛型類型更加通用!尤其是在方法調用時形參使用通配符!
     */
    public void print(List<?> list) {
        //list.add("hello");//錯誤!!!編譯器報錯,當使用通配符時,對泛型類中的參數爲泛型的方法起到了副作用,不能再使用!
        Object s = list.get(0);//正確!!!但是隻是得益於object類是所有類的父類,換成其他任何類編譯器都會報錯!說明當使用通配符時,泛型類中返回值爲泛型的方法,也作廢了!
    }


    public void fun3() {
        List<Integer> intList = new ArrayList<Integer>();
        print1(intList);
        List<Long> longList = new ArrayList<Long>();
        print1(longList);
    }
    /*
     * 給通配符添加了限定:
     *   只能傳遞Number或其子類型
     *   子類通配符對通用性產生了影響,但使用形參更加靈活
     */
    public void print1(List<? extends Number> list) {
        //list.add(new Integer(100));//錯誤!!!編譯器報錯,說明參數爲泛型的方法還是不能使用(因爲?也可能爲Long型)
        Number number = list.get(0);//正確!!!返回值爲泛型的方法可用了!
    }


    public void fun4() {
        List<Integer> intList = new ArrayList<Integer>();
        print2(intList);
        List<Number> numberList = new ArrayList<Number>();
        print2(numberList);
        List<Object> objList = new ArrayList<Object>();
        print2(objList);
    }
    /*
     * 給通配符添加了限定
     *   只能傳遞Integer類型,或其父類型
     */
    public void print2(List<? super Integer> list) {
        list.add(new Integer(100));//正確!!!參數爲泛型的方法可以使用了
        Object obj =  list.get(0);//正確!!!但是隻是得益於object類是所有類的父類,換成其他任何類編譯器都會報錯!說明返回值爲泛型的方法,還是不能使用
    }
}
複製代碼

6 泛型父類獲取子類傳遞的類型參數

複製代碼
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.junit.Test;
public class Demo1 {
    @Test
    public void fun1() {
        new B();//輸出:class B java.lang.String
    }
    @Test
    public void fun2() {
        new C();//輸出:class C java.lang.Integer
    }
}
class A<T> {
    public A() {
        //在這裏獲取子類傳遞的泛型信息,要得到一個Class!
        Class clazz = this.getClass();                        //得到子類的類型
        System.out.print(clazz+" ");
        Type type = clazz.getGenericSuperclass();            //獲取傳遞給父類參數化類型
        ParameterizedType pType = (ParameterizedType) type;    //它就是A<String>
        Type[] types = pType.getActualTypeArguments();        //它就是一個Class數組
        Class c = (Class)types[0];                            //它就是String或者Integer
        //連成一句:Class c = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        System.out.println(c.getName());
    }
}
class B extends A<String> {}
class C extends A<Integer> {}
複製代碼
發佈了1 篇原創文章 · 獲贊 12 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章