Java基礎-泛型詳解

個人博客 轉載請註明原創出處。

之因此會想來寫泛型相關的內容,是由於看到這樣的一段代碼:java

當時個人心裏是這樣的:程序員

因此就趕忙去複習了下,記錄下來。基礎不紮實,源碼看不懂啊。數組

泛型介紹

Java 泛型(generics)是 JDK 5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制容許程序員在編譯時檢測到非法的類型。泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數,在Java集合框架裏使用的很是普遍。安全

定義的重點是提供了編譯時類型安全檢測機制。好比有這樣的一個泛型類:框架

public class Generics <T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

而後寫這樣一個類:ide

public class Generics  {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

它們一樣都能存儲全部值,可是泛型類有編譯時類型安全檢測機制:this

泛型類

一個類定義了一個或多個類型變量,那麼就是泛型類。語法是在類名後用尖括號括起來,類型變量寫在裏面用逗號分開。而後就能夠在方法的返回類型、參數和域、局部變量中使用類型變量了,可是不能在有static修飾符修飾的方法或域中使用。例子:spa

類定義參考上文例子
使用形式
Generics<String> generics = new Generics<String>();
後面尖括號內的內容在Jdk7之後能夠省略
Generics<String> generics = new Generics<>();

泛型方法

一個方法定義了一個或多個類型變量,那麼就是泛型方法。語法是在方法修飾符後面、返回類型前面用尖括號括起來,類型變量寫在裏面用逗號分開。泛型方法能夠定義在普通類和泛型類中,泛型方法能夠被static修飾符修飾。
例子:code

private <U> void out(U u) {
        System.out.println(u);
    }
    
    調用形式,
    Test.<String>out("test");
    大部分狀況下<String>均可以省略,編譯器能夠推斷出來類型
    Test.out("test");

類型變量的限定

有時候咱們會有但願限定類型變量的狀況,好比限定指定的類型變量須要實現List接口,這樣咱們就能夠在代碼對類型變量調用List接口裏的方法,而不用擔憂會沒有這個方法。對象

public class Generics <T extends List> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public void add(Object u) {
        value.add(u);
    }

    public static void main(String[] args) {
        Generics<List> generics = new Generics<>();
        generics.setValue(new ArrayList<>());
        generics.add("ss");
        System.out.println(generics.getValue());
    }
    
}

限定的語法是在類型變量的後面加extends關鍵字,而後加限定的類型,多個限定的類型要用&分隔。類型變量和限定的類型能夠是類也能夠是接口,由於Java中類只能繼承一個類,因此限定的類型是類的話必定要在限定列表的第一個。

類型擦除

類型擦除是爲了兼容而搞出來的,大意就是在虛擬機裏是沒有泛型類型,泛型只存在於編譯期間。泛型類型變量會在編譯後被擦除,用第一個限定類型替換(沒有限定類型的用Object替換)。上文中的Generics <T>泛型類被擦除後會產生對應的一個原始類型:

public class Generics {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}

之因此咱們能設置和返回正確的類型是由於編譯器自動插入了類型轉換的指令。

public static void main(String[] args) {
        Generics<String> generics = new Generics<>();
        generics.setValue("ss");
        System.out.println(generics.getValue());
    }
    
    javac Generics.java
    javap -c Generics
    編譯後的代碼
     public static void main(java.lang.String[]);
        Code:
       0: new           #3                  // class generics/Generics
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #5                  // String ss
      11: invokevirtual #6                  // Method setValue:(Ljava/lang/Object;)V
      14: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_1
      18: invokevirtual #8                  // Method getValue:()Ljava/lang/Object;
      21: checkcast     #9                  // class java/lang/String
      24: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return

咱們能夠看到在21行插入了一條類型轉換的指令。

類型擦除還帶來了另外一個問題,若是咱們有一個類繼承了泛型類並重寫了父類的方法:

public class SubGenerics extends Generics<String> {

    @Override
    public void setValue(String value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        Generics<String> generics = new SubGenerics();
        generics.setValue("ss");
    }

}

由於類型擦除因此SubGenerics實際上有兩個setValue方法,SubGenerics本身的setValue(String value)方法和從Generics繼承來的setValue(Object value)方法。例子中的generics引用的是SubGenerics對象,因此咱們但願調用的是SubGenerics.setValue。爲了保證正確的多態性,編譯器在SubGenerics類中生成了一個橋方法:

public void setValue(Object value) {
    setValue((String) value);
}

咱們能夠編譯驗證下:

Compiled from "SubGenerics.java"
public class generics.SubGenerics extends generics.Generics<java.lang.String> {
  public generics.SubGenerics();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method generics/Generics."<init>":()V
       4: return

  public void setValue(java.lang.String);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_1
       4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       7: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #4                  // class generics/SubGenerics
       3: dup
       4: invokespecial #5                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #6                  // String ss
      11: invokevirtual #7                  // Method generics/Generics.setValue:(Ljava/lang/Object;)V
      14: return

  public void setValue(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #8                  // class java/lang/String
       5: invokevirtual #9                  // Method setValue:(Ljava/lang/String;)V
       8: return
}
引用《Java核心技術 卷一》

總之,須要記住有關 Java 泛型轉換的事實:
1.虛擬機中沒有泛型,只有普通的類和方法。
2.全部的類型參數都用它們的限定類型替換。
3.橋方法被合成來保持多態。
4.爲保持類型安全性,必要時插人強制類型轉換。

約束與侷限性

  • 類型變量不能是基本變量,好比int,double等。應該使用它們的包裝類Integer,Double
  • 虛擬機中沒有泛型,因此不能使用運行時的類型查詢,好比 if (generics instanceof Generics<String>) // Error
  • 由於類型擦除的緣由,不能建立泛型類數組,好比Generics<String>[] generics = new Generics<String>[10]; // Error
  • 不能實例化類型變量,好比new T(...) new T[...] 或 T.class

通配符類型

通配符類型和上文中的類型變量的限定有些相似,區別是通配符類型是運用在聲明的時候而類型變量的限定是在定義的時候。好比通配符類型Generics<? extends List>表明任何泛型Generics類型的類型變量是ListList的子類。

Generics<? extends List> generics = new Generics<ArrayList>();

不過這樣聲明以後Generics的方法也發生了變化,變成了

這樣就致使了不能調用setValue方法

getValue方法是正常的

超類型限定

通配符限定還能夠限定超類,好比通配符類型Generics<? super ArrayList>表明任何泛型Generics類型的類型變量是ArrayListArrayList的超類。

Generics<? super ArrayList> generics = new Generics<List>();

一樣的,Generics的方法也發生了變化,變成了

調用getValue方法只能賦值給Object變量

調用setValue方法只能傳入ArrayListArrayList的子類,超類List,Object等都不行

反射和泛型

雖然由於類型擦除,在虛擬機裏是沒有泛型的。不過被擦除的類仍是保留了一些關於泛型的信息,可使用反射相關的Api來獲取。

相似地,看一下泛型方法

public static <T extends Comparable<? super T>> T min(T[] a)

這是擦除後

public static Comparable min(Coniparable[] a)

可使用反射 API 來肯定:

  • 這個泛型方法有一個叫作T的類型參數。
  • 這個類型參數有一個子類型限定, 其自身又是一個泛型類型。
  • 這個限定類型有一個通配符參數。
  • 這個通配符參數有一個超類型限定。
  • 這個泛型方法有一個泛型數組參數。

後記

週一就建好的草稿,到了星期天才寫好,仍是刪掉了一些小節狀況下,怕是拖延症晚期了......不過也是由於泛型的內容夠多,雖然平常業務裏不多本身去寫泛型相關的代碼,可是在閱讀類庫源碼時要是不懂泛型就步履維艱了,特別是集合相關的。此次的大部份內容都是《Java核心技術 卷一》裏的,這但是本關於Java基礎的好書。不過仍是老規矩,光讀可不行,仍是要用本身的語言記錄下來。衆所周知,人類的本質是復讀機,把好書裏的內容重複一遍,就等於我也有責任了!

相關文章
相關標籤/搜索