Java泛型

java泛型在平時開發中或者閱讀項目源碼的時候都見過他,咱們雖然知道它,可是大多數咱們也是對他並不太瞭解。這個星期我花了點時間從新複習了一下泛型的一些內容,這篇文章是對複習筆記的簡單整理,裏面內容只是整理一些咱們常常忽視或者有很模糊的知識點。java

  1. 概述
  2. 類型擦除
  3. 泛型晉級使用
  4. 通配符
  5. 其餘

1. 概述

泛型指的是能夠將類型做爲參數進行傳遞,其本質上就是類型參數化。好比:咱們平時定義一個方法的時候,常會指定要傳入一個具體類對象做爲參數。而若是使用泛型,那麼這個具體傳入類的對象,就能夠指定爲某個類型,而沒必要指定具體的類。也就是咱們將某個類型做爲參數進行傳遞了。bash

//普通方法
public void testValue(String s) {}
//泛型方法
public <T> void testValue(T t) {}
複製代碼

他與使用Object有什麼區別?

若是咱們使用Object,就要將傳入的類型強制轉換成咱們須要的類型,若是傳入的類型不匹配將會致使程序包ClassCastException異常。好比下面的代碼,testObj()傳入的是int類型的值,程序在執行的時候將會出錯:ui

public void testObj(Object o){
    String name= (String) o;
}
複製代碼

咱們能夠經過泛型未來實現這樣的需求:this

public <O extends String> void testObj(O o) {
    String name = o;
}
複製代碼

使用泛型有哪些好處?

  • 它能夠避免類型強制轉換,而引發的程序異常。
  • 能夠是代碼更加簡潔易度。
  • 是代碼更加靈活,可定製型強。

2. 類型擦除

泛型值存在於編譯期,代碼在進入虛擬機後泛型就會會被擦除掉,這個者特性就叫作類型擦除。當泛型被擦除後,他有兩種轉換方式,第一種是若是泛型沒有設置類型上限,那麼將泛型轉化成Object類型,第二種是若是設置了類型上限,那麼將泛型轉化成他的類型上限。spa

//未指定上限
public class Test1<T> {
    T t;
    public T getValue() {
        return t;
    }
    public void setVale(T t) {
        this.t = t;
    }
}
//指定上限
public class Test2<T extends String> {
    T t;
    public T getT() {
        return t;
    }
    public void setT(T t) {
        this.t = t;
    }
}
//經過反射調用獲取他們的屬性類型
@Test
public void testType1() {
    Test1<String> test1 = new Test1<>();
    test1.setVale("11111");
    Class<? extends Test1> aClass = test1.getClass();
    for (Field field : aClass.getDeclaredFields()) {
        System.out.println("Test1屬性:" + field.getName() + "的類型爲:" + field.getType().getName());
    }

    Test2 test2 = new Test2();
    test2.setT("2222");
    Class<? extends Test2> aClass2 = test2.getClass();
    for (Field field : aClass2.getDeclaredFields()) {
        System.out.println("test2屬性:" + field.getName() + "的類型爲:" + field.getType().getName());
    }
}
複製代碼

上面方法打印的結果:.net

Test1屬性:t的類型爲:java.lang.Object
Test2屬性:t的類型爲:java.lang.String
複製代碼

3. 泛型晉級使用

繼承關係

即設置泛型上限,傳入的泛型必須是String類型或者是他的子類code

這裏有一個小小的坑,感謝覺得熱心網友的反饋。若是讀者看到這段請想想String的特性。這個問題在文章末尾的評論去有答案。對象

public <T extends String> void testType(T t) {}
複製代碼

依賴關係的使用

泛型間能夠存在依賴關係,好比下面的C是繼承自E。即傳入的類型是E類型或者是E類型的子類blog

public <E, C extends E> void testDependys(E e, C c) {}
複製代碼

4. 通配符

當咱們不知道或者不關心實際操做類型的時候咱們可使用無限通配符,當咱們不指定或者不關心操做類型,可是又想進行必定範圍限制的時候,咱們能夠經過添加上限下限來起到限制做用。繼承

<?>無限通配符

無限通配符表示的是未知類型,表示不關心或者不能肯定實際操做的類型,通常配合容器類使用。

public void testV(List<?> list) {}
複製代碼

須要注意的是: 無限通配符只能讀的能力,沒有寫的能力。

public void testV(List<?> list) {
      Object o = list.get(0);
    //編譯器不容許該操做
   // list.add("jaljal");
}
複製代碼

上面的List<?>爲無限通配符,他只能使用get()獲取元素,但不能使用add()方法添加元素。(即便修改元素也不被容許)

<? extends T>

定義了上限,期只有讀的能力。此方式表示參數化的類型多是所指定的類型,或者是此類型的子類

//t1要麼是Test2,要麼是Test2的子類
public void testC(Test1<? extends Test2> t1) {
    Test2 value = t1.getValue();
    System.out.println("testC中的:" + value.getT());
}
複製代碼

<? super T>

定義了下限,有讀的能力以及部分寫的能力,子類能夠寫入父類。此方式表示參數化的類型多是指定的類型,或者是此類型的父類

//t1要麼是Test5,要麼是Test5的父類
public void testB(Test1<? super Test5> t1) {
    //子類代替父類
    Test2 value = (Test2) t1.getValue();
    System.out.println(value.getT());
}
複製代碼

通配符不能用做返回值

若是返回值依賴類型參數,不能使用通配符做爲返回值。可使用類型參數返回方式:

public <T> T testA(T t, Test1<T> test1) {
    System.out.println("這是傳入的T:" + t);
    t = test1.t;
    System.out.println("這是賦值後的T:" + t);
    return t;
}
複製代碼
  • 要從泛型類取數據時,用extends;
  • 要往泛型類寫數據時,用super;
  • 既要取又要寫,就不用通配符(即extends與super都不用)。

泛型中只有通配符可使用super關鍵字,類型參數不支持 這種寫法

5. 其餘

何時使用通配符

  • 通配符形式和類型參數常常配合使用
  • 類型參數的形式均可以替代通配符的形式
  • 能用通配符的就用通配符,由於通配符形式上每每更爲簡單可讀性也更好
  • 類型參數之間有依賴關係返回值依賴類型參數或者須要寫操做,則只能用類型參數

查看源碼使用

若是想查找源碼中的相關使用能夠Collections類的的下面這些方法:

public static <T extends Comparable<? super T>> void sort(List<T> list)

public static <T> void sort(List<T> list, Comparator<? super T> c)

public static <T> void copy(List<? super T> dest, List<? extends T> src)

public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)
複製代碼

參考

java 泛型,你瞭解類型擦除嗎?

深刻理解 Java 泛型

(36) 泛型 (中) - 解析通配符 / 計算機程序的思惟邏輯

相關文章
相關標籤/搜索