答:java
使用泛型機制編寫的代碼要比那些雜亂的使用Object
變量,而後再進行強制類型轉換的代碼具備更好的安全性和可讀性,也就是說使用泛型機制編寫的代碼能夠被不少不一樣類型的對象所重用。程序員
<!-- more -->數組
答:安全
在Java增長泛型機制以前就已經有一個ArrayList
類,這個ArrayList
類的泛型概念是使用繼承來實現的。app
public class ArrayList { private Object[] elementData; public Object get(int i) {....} public void add(Object o) {....} }
這個類存在兩個問題:jvm
ArrayList files = new ArrayList(); files.add(new File("")); String filename = (String)files.get(0);
對於這個調用,編譯和運行都不會出錯,可是當咱們在其餘地方使用get方法獲取剛剛存入的這個File
對象強轉爲String
類型的時候就會產生一個錯誤。spa
泛型對於這種問題的解決方案是提供一個類型參數。code
ArrayList<String> files = new ArrayList<>();
這樣可使代碼具備更好的可讀性,咱們一看就知道這個數據列表中包含的是String
對象。
編譯器也能夠很好地利用這個信息,當咱們調用get
的時候,不須要再使用強制類型轉換,編譯器就知道返回值類型爲String
,而不是Object
:對象
String filename = files.get(0);
編譯器還知道ArrayList<String>
中add
方法中有一個類型爲String
的參數。這將比使用Object
類型的參數安全一些,如今編譯器能夠檢查,避免插入錯誤類型的對象:blog
files.add(new File(""));
這樣的代碼是沒法經過編譯的,出現編譯錯誤比類在運行時出現類的強制類型轉換異常要好得多。
一個泛型類就是具備一個或多個類型變量的類,對於這個類來講,咱們只關注泛型,而不會爲數據存儲的細節煩惱。
public class Couple<T> { private T one; private T two; }
Singer
類引入了一個類型變量T,用尖括號括起來,並放在類名的後面。泛型類能夠有多個類型變量:
public class Couple<T, U> {...}
類定義中的類型變量是指定方法的返回類型以及域和局部變量的類型
//域 private T one; //返回類型 public T getOne() { return one; } //局部變量 public void setOne(T newValue) { one = newValue; }
使用具體的類型代替類型變量就能夠實例化泛型類型:
Couple<Rapper>
泛型類能夠當作是普通類的工廠,打個比方:我用泛型造了一個模型,具體填充什麼樣的材質,由使用者去作決定。
答:
泛型方法能夠定義在普通類中,也能夠定義在泛型類中,類型變量是放在修飾符的後面,返回類型的前面。
咱們來看一個泛型方法的實例:
class ArrayUtil { public static <T> T getMiddle(T...a){ return a[a.length / 2]; } }
當調用一個泛型方法時,在方法名前的尖括號中放入具體的類型:
String middle = ArrayUtil.<String>getMiddle("a","b","c");
在這種狀況下,方法調用中能夠省略<String>
類型參數,編譯器會使用類型推斷來推斷出所調用的方法,也就是說能夠這麼寫:
String middle = ArrayAlg.getMiddle("a","b","c");
E V T K ?
這些是什麼答:
E
——Element 表示元素 特性是一種枚舉T
——Type 類,是指Java類型K
—— Key 鍵V
——Value 值?
——在使用中表示不肯定類型答:
一個類型變量或通配符能夠有多個限定,例如:
<T extends Serializable & Cloneable>
單個類型變量的多個限定類型使用&
分隔,而,
用來分隔多個類型變量。
<T extends Serializable,Cloneable>
在類型變量的繼承中,能夠根據須要擁有多個接口超類型,可是限定中至多有一個類。若是用一個類做爲限定,它一定是限定列表中的第一個。
類型變量的限定是爲了限制泛型的行爲,指定了只有實現了特定接口的類才能夠做爲類型變量去實例化一個類。
答:
首先,咱們來看一個類和它的子類,好比 Singer
和 Rapper
。可是Couple<Rapper>
卻並非Couple<Singer>
的一個子類。
不管S和T有什麼聯繫,Couple<S>
與Couple<T>
沒有什麼聯繫。
這裏須要注意泛型和Java數組之間的區別,能夠將一個Rapper[]
數組賦給一個類型爲Singer[]
的變量:
Rapper[] rappers = ...; Singer[] singer = rappers;
然而,數組帶有特別的保護,若是試圖將一個超類存儲到一個子類數組中,虛擬機會拋出ArrayStoreException
異常。
答:
通配符類型中,容許類型參數變化。好比,通配符類型:
Couple<? extends Singer>
表示任何泛型類型,它的類型參數是Singer
的子類,如Couple<Rapper>
,但不會是Couple<Dancer>
。
假如如今咱們須要編寫一個方法去打印一些東西:
public static void printCps(Couple<Rapper> cps) { Rapper one = cp.getOne(); Rapper two = cp.getTwo(); System.out.println(one.getName() + " & " + two.getName() + " are cps."); }
正如前面所講到的,不能將Couple<Rapper>
傳遞給這個方法,這一點很受限制。解決的方案很簡單,使用通配符類型:
public static void printCps(Couple< ? extends Singer> cps)
Couple<Rapper>
是Couple< ? extends Singer>
的子類型。
咱們接下來來考慮另一個問題,使用通配符會經過Couple< ? extends Singer>
的引用破壞Couple<Rapper>
嗎?
Couple<Rapper> rapper = new Couple<>(rapper1, rapper2); Couple<? extends Singer> singer = rapper; player.setOne(reader);
這樣可能會引發破壞,可是當咱們調用setOne
的時候,若是調用的不是Singer
的子類Rapper
類的對象,而是其餘Singer
子類的對象,就會出錯。
咱們來看一下Couple<? extends Singer>
的方法:
? extends Singer getOne(); void setOne(? extends Singer);
這樣就會看的很明顯,由於若是咱們去調用setOne()
方法,編譯器之能夠知道是某個Singer
的子類型,而不能肯定具體是什麼類型,它拒絕傳遞任何特定的類型,由於 ? 不能用來匹配。
可是使用getOne
就不存在這個問題,由於咱們無需care它獲取到的類型是什麼,但必定是Singer
的子類。
通配符限定與類型變量限定很是類似,可是通配符類型還有一個附加的能力,便可以指定一個超類型限定:
? super Rapper
這個通配符限制爲Rapper
的全部父類,爲何要這麼作呢?帶有超類型限定的通配符的行爲與子類型限定的通配符行爲徹底相反,能夠爲方法提供參數,可是卻不能獲取具體的值,即訪問器是不安全的,而更改器方法是安全的:
編譯器沒法知道setOne
方法的具體類型,所以調用這個方法時不能接收類型爲Singer
或Object
的參數。只能傳遞Rapper
類型的對象,或者某個子類型(Reader
)對象。並且,若是調用getOne
,不能保證返回對象的類型。
總結一下:
帶有超類型限定的通配符能夠向泛型對象寫入,帶有子類型限定的通配符能夠從泛型對象讀取。
答:
不管什麼時候定義一個泛型類型,都自動提供了一個相應的原始類型。原始類型的名字就是刪去類型參數後的泛型類型名。擦除類型變量,並替換成限定類型(沒有限定的變量用Object
)。這樣作的目的是爲了讓非泛型的Java
程序在後續支持泛型的 jvm
上還能夠運行(向後兼容)
Couple<Singer> cps = ...; Singer one = cp.getOne();
擦除cp.getOne
的返回類型後將返回Object
類型。編譯器自動插入Singer
的強制類型轉換。也就是說,編譯器把這個方法調用編譯爲兩條虛擬機指令:
對原始方法cp.getOne
的調用
將返回的Object類型強制轉換爲Singer
類型。
//咱們寫的代碼 Singer one = cps.one; //編譯器作的事情 Singer one = (Singer)cps.one;
答:
類型擦除會出如今泛型方法中,程序員一般認爲下述的泛型方法
public static <T extends Comparable> T min(T[] a)
是一個完整的方法族,而擦除類型以後,只剩下一個方法:
public static Comparable min(Comparable[] a)
這個時候類型參數T已經被擦除了,只留下了限定類型Comparable
。
可是方法的擦除會帶來一些問題:
class Coupling extends Couple<People> { public void setTwo(People people) { super.setTwo(people); } }
擦除後:
class Coupling extends Couple { public void setTwo(People People) {...} }
這時,問題出現了,存在另外一個從Couple
類繼承的setTwo
方法,即:
public void setTwo(Object two)
這顯然是一個不一樣的方法,由於它有一個不一樣類型的參數(Object
),而不是People
。
Coupling coupling = new Coupling(...); Couple<People> cp = interval; cp.setTwo(people);
這裏,但願對setTwo
的調用具備多態性,並調用最合適的那個方法。因爲cp
引用Coupling
對象,因此應該調用Coupling.setTwo
。問題在於類型擦除與多態發生了衝突。要解決這個問題,就須要編譯器在Coupling
類中生成一個橋方法:
public void setTwo(Object second) { setTwo((People)second); }
變量cp
已經聲明爲類型Couple<LocalDate>
,而且這個類型只有一個簡單的方法叫setTwo
,即setTwo(Object)
。虛擬機用cp
引用的對象調用這個方法。這個對象是Coupling
類型的,因此會調用Coupling.setTwo(Object)
方法。這個方法是合成的橋方法。它會調用Coupling.setTwo(Date)
,這也正是咱們所指望的結果。
因此,咱們要記住關於Java泛型轉換的幾個點: