個人博客 轉載請註明原創出處。java
之因此會想來寫泛型相關的內容,是由於看到這樣的一段代碼:程序員
當時個人心裏是這樣的:數組
因此就趕忙去複習了下,記錄下來。基礎不紮實,源碼看不懂啊。安全
Java 泛型(generics)是 JDK 5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制容許程序員在編譯時檢測到非法的類型。泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數,在Java集合框架裏使用的很是普遍。bash
定義的重點是提供了編譯時類型安全檢測機制。好比有這樣的一個泛型類:markdown
public class Generics <T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } 複製代碼
而後寫這樣一個類:框架
public class Generics { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } 複製代碼
它們一樣都能存儲全部值,可是泛型類有編譯時類型安全檢測機制:ide
一個類定義了一個或多個類型變量,那麼就是泛型類。語法是在類名後用尖括號括起來,類型變量寫在裏面用逗號分開。而後就能夠在方法的返回類型、參數和域、局部變量中使用類型變量了,可是不能在有static
修飾符修飾的方法或域中使用。例子:oop
類定義參考上文例子
使用形式
Generics<String> generics = new Generics<String>();
後面尖括號內的內容在Jdk7之後能夠省略
Generics<String> generics = new Generics<>();
複製代碼
一個方法定義了一個或多個類型變量,那麼就是泛型方法。語法是在方法修飾符後面、返回類型前面用尖括號括起來,類型變量寫在裏面用逗號分開。泛型方法能夠定義在普通類和泛型類中,泛型方法能夠被static
修飾符修飾。 例子:this
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
類型的類型變量是List
和List
的子類。
Generics<? extends List> generics = new Generics<ArrayList>();
不過這樣聲明以後Generics
的方法也發生了變化,變成了
這樣就致使了不能調用setValue
方法
而getValue
方法是正常的
通配符限定還能夠限定超類,好比通配符類型Generics<? super ArrayList>
表明任何泛型Generics
類型的類型變量是ArrayList
和ArrayList
的超類。
Generics<? super ArrayList> generics = new Generics<List>();
一樣的,Generics
的方法也發生了變化,變成了
調用getValue
方法只能賦值給Object
變量
調用setValue
方法只能傳入ArrayList
和ArrayList
的子類,超類List,Object
等都不行
雖然由於類型擦除,在虛擬機裏是沒有泛型的。不過被擦除的類仍是保留了一些關於泛型的信息,可使用反射相關的Api
來獲取。
相似地,看一下泛型方法
public static <T extends Comparable<? super T>> T min(T[] a)
這是擦除後
public static Comparable min(Coniparable[] a)
可使用反射 API 來肯定:
T
的類型參數。週一就建好的草稿,到了星期天才寫好,仍是刪掉了一些小節狀況下,怕是拖延症晚期了......不過也是由於泛型的內容夠多,雖然平常業務裏不多本身去寫泛型相關的代碼,可是在閱讀類庫源碼時要是不懂泛型就步履維艱了,特別是集合相關的。此次的大部份內容都是《Java核心技術 卷一》裏的,這但是本關於Java
基礎的好書。不過仍是老規矩,光讀可不行,仍是要用本身的語言記錄下來。衆所周知,人類的本質是復讀機,把好書裏的內容重複一遍,就等於我也有責任了!