什么是泛型
Java泛型( generics) 是JDK 5中引?的?個新特性英岭, 允許在定義類和接口的時候使?類型參數(shù)( type parameter) 俄占。
聲明的類型參數(shù)在使?時?具體的類型來替換萝招。 泛型最主要的應(yīng)?是在JDK 5中的新集合類框架中。
泛型最?的好處是可以提?代碼的復(fù)?性奕塑。 以List接?為例械姻,我們可以將String、 Integer等類型放?List中恩脂, 如不?泛型帽氓, 存放String類型要寫?個List接口, 存放Integer要寫另外?個List接口俩块, 泛型可以很好的解決這個問題黎休。
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型測試","item = " + item);
}
程序運行出現(xiàn)異常java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
。這是因為我們第二個存放的元素是Integer
類型不能被強轉(zhuǎn)為String
玉凯。
ArrayList可以看成是一個什么都能存的容器势腮。假設(shè)我們每次取相應(yīng)的類型數(shù)據(jù)都需要相對應(yīng)的工具。ArrayList中存了String漫仆,Integer.....捎拯。我們可以使用String相對應(yīng)的工具取出String類型數(shù)據(jù)。但是我們卻不能用它來取出Integer型的數(shù)據(jù)盲厌。(強取報錯)
而且我們很多時候從這個容器中拿東西是不知道每次要取出的數(shù)據(jù)的類型署照。所以我們就需要一個專門存放特定一種類型的容器。
List<String> list = new ArrayList<>();
只存String類型的容器
List<Integer> list = new ArrayList<>();
只存Integer類型的容器
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在編譯階段吗浩,編譯器就會報錯
這就是泛型的最常用的使用建芙。
泛型有三種使用方式,分別為:泛型類懂扼、泛型接口禁荸、泛型方法
泛型的使用
<mark>泛型的類型參數(shù)只能是類類型(包括自定義類),不能是簡單類型</mark>
泛型類
泛型類型用于類的定義中阀湿,被稱為泛型類赶熟。通過泛型可以完成對一組類的操作對外開放相同的接口。最典型的就是各種容器類炕倘,如:List钧大、Set、Map罩旋。
class 類名稱 <泛型標識:可以隨便寫任意標識號啊央,標識指定的泛型的類型>{
private 泛型標識 /*(成員變量類型)*/ var;
.....
}
}
一個最普通的泛型類
//此處T可以隨便寫為任意標識眶诈,常見的如T、E瓜饥、K逝撬、V等形式的參數(shù)常用于表示泛型
//在實例化泛型類時,必須指定T的具體類型
public class Generic<T>{
//key這個成員變量的類型為T,T的類型由外部指定
private T key;
public Generic(T key) { //泛型構(gòu)造方法形參key的類型也為T乓土,T的類型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值類型為T宪潮,T的類型由外部指定
return key;
}
}
//泛型的類型參數(shù)只能是類類型(包括自定義類),不能是簡單類型
//傳入的實參類型需與泛型的類型參數(shù)類型相同趣苏,即為Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);
//傳入的實參類型需與泛型的類型參數(shù)類型相同狡相,即為String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型測試","key is " + genericInteger.getKey());
Log.d("泛型測試","key is " + genericString.getKey());
泛型測試: key is 123456
泛型測試: key is key_vlaue
定義的泛型類,就一定要傳入泛型類型實參么食磕?
<mark>并不是這樣尽棕,在使用泛型的時候如果傳入泛型實參,則會根據(jù)傳入的泛型實參做相應(yīng)的限制彬伦,此時泛型才會起到本應(yīng)起到的限制作用滔悉。如果不傳入泛型類型實參的話,在泛型類中使用泛型的方法或成員變量定義的類型可以為任何的類型单绑。</mark>
//并未傳人泛型實參 例如 Generic<String> generic = new Generic<>();
Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);
Log.d("泛型測試","key is " + generic.getKey());
Log.d("泛型測試","key is " + generic1.getKey());
Log.d("泛型測試","key is " + generic2.getKey());
Log.d("泛型測試","key is " + generic3.getKey());
泛型測試: key is 111111
泛型測試: key is 4444
泛型測試: key is 55.55
泛型測試: key is false
<mark>不能對確切的泛型類型使用instanceof操作回官。如下面的操作是非法的,編譯時會出錯搂橙。</mark>
List<Integer> list = new ArrayList<>();
//可以使用 true
if (list instanceof List){}
//編譯報錯 確切的泛型類型List<Integer>
if (list instanceof List<Integer>){}
泛型接口
泛型接口與泛型類的定義及使用基本相同歉提。
//定義一個泛型接口
public interface Generator<T> {
public T next();
}
當實現(xiàn)泛型接口的類,未傳入泛型實參時
//未傳入泛型實參時份氧,與泛型類的定義相同唯袄,在聲明類的時候,需將泛型的聲明也一起加到類中
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
//如果不聲明泛型 編譯器會報錯:"Unknown class"
class FruitGenerator implements Generator<T>{}
當實現(xiàn)泛型接口的類蜗帜,傳入泛型實參時
//在實現(xiàn)類實現(xiàn)泛型接口時恋拷,如已將泛型類型傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型(Idea我?guī)臀覀冏詣犹鎿Q)
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
泛型方法
泛型類厅缺,是在實例化類的時候指明泛型的具體類型蔬顾;泛型方法,是在調(diào)用方法的時候指明泛型的具體類型 湘捎。
下面是定義泛型方法的規(guī)則:
- 所有泛型方法聲明都有一個類型參數(shù)聲明部分(由尖括號分隔)诀豁,該類型參數(shù)聲明部分在方法返回類型之前(在下面例子中的
<E>
)。 - 每一個類型參數(shù)聲明部分包含一個或多個類型參數(shù)窥妇,參數(shù)間用逗號隔開舷胜。一個泛型參數(shù),也被稱為一個類型變量活翩,是用于指定一個泛型類型名稱的標識符烹骨。
- 類型參數(shù)能被用來聲明返回值類型翻伺,并且能作為泛型方法得到的實際參數(shù)類型的占位符。
- 泛型方法體的聲明和其他方法一樣沮焕。注意類型參數(shù)只能代表引用型類型吨岭,不能是原始類型(像int,double,char的等)。
具體看這篇文章中寫的峦树。https://blog.csdn.net/s10461/article/details/53941091辣辫。太多了,分多種情況魁巩。
泛型通配符
無邊界的通配符急灭,固定上邊界的通配符,固定下邊界的通配符
無邊界通配符
一般是使用?代替具體的類型參數(shù)歪赢。例如 List<?> 在邏輯上是List<String>,List<Integer>
等所有List<具體類型實參>的父類化戳。
類型通配符一般是使用单料?代替具體的類型實參埋凯,注意了,此處’扫尖?’是類型實參白对,而不是類型形參 。重要說三遍换怖!此處’甩恼?’是類型實參,而不是類型形參 沉颂! 此處’条摸?’是類型實參,而不是類型形參 铸屉!再直白點的意思就是钉蒲,此處的?和Number彻坛、String顷啼、Integer一樣都是一種實際的類型,可以把昌屉?看成所有類型的父類钙蒙。是一種真實的類型。
可以解決當具體類型不確定的時候间驮,這個通配符就是 ? 躬厌;當操作類型時,不需要使用類型的具體功能時竞帽,只使用Object類中的功能扛施。那么可以用 ? 通配符來表未知類型偏陪。
class Base{}
class Sub extends Base{}
Sub sub = new Sub();
Base base = sub;
上面代碼顯示,Base 是 Sub 的父類煮嫌,它們之間是繼承關(guān)系笛谦,所以 Sub 的實例可以給一個 Base 引用賦值,那么
List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;
最后一行代碼成立嗎昌阿?編譯會通過嗎饥脑?答案是否定的。
編譯器不會讓它通過的懦冰。Sub 是 Base 的子類灶轰,不代表 List<Sub>
和 List<Base>
有繼承關(guān)系。
但是刷钢,在現(xiàn)實編碼中笋颤,確實有這樣的需求,希望泛型能夠處理某一范圍內(nèi)的數(shù)據(jù)類型内地,比如某個類和它的子類伴澄,對此 Java 引入了通配符這個概念。
無限定通配符經(jīng)常與容器類配合使用阱缓,它其中的 ? 其實代表的是未知類型非凌,所以涉及到 ? 時的操作,一定與具體類型無關(guān)荆针。
方法內(nèi)的參數(shù)是被無限定通配符修飾的 Collection 對象敞嗡,它隱略地表達了一個意圖或者可以說是限定,那就是 testWidlCards() 這個方法內(nèi)部無需關(guān)注 Collection 中的真實類型航背,因為它是未知的喉悴。所以,你只能調(diào)用 Collection 中與類型無關(guān)的方法
public class TestWildCards {
public void testWildCards(Collection<?> collection){
collection.add(123);//報錯
collection.add("hello");//報錯
collection.add(new Object());//報錯
collection.size();
}
}
有人說玖媚,<?>
提供了只讀的功能箕肃,也就是它刪減了增加具體類型元素的能力,只保留與具體類型無關(guān)的功能最盅。它不管裝載在這個容器內(nèi)的元素是什么類型突雪,它只關(guān)心元素的數(shù)量、容器是否為空涡贱?我想這種需求還是很常見的吧咏删。
有同學(xué)可能會想,<?>
既然作用這么渺小问词,那么為什么還要引用它呢督函? ?
個人認為,提高了代碼的可讀性,程序員看到這段代碼時辰狡,就能夠迅速對此建立極簡潔的印象柬帕,能夠快速推斷源碼作者的意圖舱殿。(延曙?跌捆??叫倍?這或許就是高手這樣寫代碼吧)
上下界限定符
<? extends T>
和<? super T>
是Java泛型中的“通配符(Wildcards)”和“邊界(Bounds)”的概念偷卧。
<mark><? extends T>
:是指 “上界通配符(Upper Bounds Wildcards)”,即泛型中的類必須為當前類的子類或當前類吆倦。</mark>
<mark><? super T>
:是指 “下界通配符(Lower Bounds Wildcards)”听诸,即泛型中的類必須為當前類或者其父類。</mark>
public class Food {}
public class Fruit extends Food {}
public class Apple extends Fruit {}
public class Banana extends Fruit{}
public class GenericTest {
public void testExtends(List<? extends Fruit> list){
//報錯,extends為上界通配符,只能取值,不能放.
//因為Fruit的子類不只有Apple還有Banana,這里不能確定具體的泛型到底是Apple還是Banana蚕泽,所以放入任何一種類型都會報錯
//list.add(new Apple());
//可以正常獲取
Fruit fruit = list.get(1);
}
public void testSuper(List<? super Fruit> list){
//super為下界通配符晌梨,可以存放元素,但是也只能存放當前類或者子類的實例须妻,以當前的例子來講仔蝌,
//無法確定Fruit的父類是否只有Food一個(Object是超級父類)
//因此放入Food的實例編譯不通過
list.add(new Apple());
// list.add(new Food());
Object object = list.get(1);
}
}
extends為上界通配符,只能取值,不能放.
super為下界通配符,可以存放元素璧南,但是也只能存放當前類或者子類的實例掌逛,以當前的例子來講。
在testExtends方法中司倚,因為泛型中用的是extends,在向list中存放元素的時候篓像,我們并不能確定List中的元素的具體類型动知,即可能是Apple也可能是Banana。因此調(diào)用add方法時员辩,不論傳入new Apple()還是new Banana()盒粮,都會出現(xiàn)編譯錯誤。
理解了extends之后奠滑,再看super就很容易理解了丹皱,即我們不能確定testSuper方法的參數(shù)中的泛型是Fruit的哪個父類,因此在調(diào)用get方法時只能返回Object類型宋税。結(jié)合extends可見摊崭,在獲取泛型元素時,使用extends獲取到的是泛型中的上邊界的類型(本例子中為Fruit),范圍更小杰赛。
在使用泛型時呢簸,存取元素時用super,獲取元素時,用extends。
頻繁往外讀取內(nèi)容的根时,適合用上界Extends瘦赫。經(jīng)常往里插入的,適合用下界Super蛤迎。
K T V E ? object等的含義
- T - Type(Java 類)
- K - Key(鍵)
- V - Value(值)
- N - Number(數(shù)值類型)
- 确虱? - 表示不確定的java類型(無限制通配符類型)
- Object - 是所有類的根類,任何類的對象都可以設(shè)置給該Object引用變量替裆,使用的時候可能需要類型強制轉(zhuǎn)換蝉娜,但是用使用了泛型T、E等這些標識符后扎唾,在實際用之前類型就已經(jīng)確定了召川,不需要再進行類型強制轉(zhuǎn)換。
類型擦除
類型擦除(type erasue)指的是通過類型參數(shù)合并胸遇,將泛型類型實例關(guān)聯(lián)到同一份字節(jié)碼上荧呐。編譯器只為泛型類型生成一份字節(jié)碼,并將其實例關(guān)聯(lián)到這份字節(jié)碼上纸镊。類型擦除的關(guān)鍵在于從泛型類型中清除類型參數(shù)的相關(guān)信息倍阐,并且再必要的時候添加類型檢查和類型轉(zhuǎn)換的方法。
類型擦除可以簡單的理解為將泛型java代碼轉(zhuǎn)換為普通java代碼逗威,只不過編譯器更直接點峰搪,將泛型java代碼直接轉(zhuǎn)換成普通java字節(jié)碼。
先給大家奉上一道經(jīng)典的測試題凯旭。
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());//ture
面的代碼中涉及到了泛型概耻,而輸出的結(jié)果緣由是類型擦除。
類型擦除的主要過程如下:
- 將所有的泛型參數(shù)用其最左邊界(最頂級的父類型)類型替換罐呼。
- 移除所有的類型參數(shù)鞠柄。
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("name", "hollis");
map.put("age", "22");
System.out.println(map.get("name"));
System.out.println(map.get("age"));
}
反編譯后
public static void main(String[] args) {
Map map = new HashMap();
map.put("name", "hollis");
map.put("age", "22");
System.out.println((String) map.get("name"));
System.out.println((String) map.get("age"));
}
我們發(fā)現(xiàn)泛型都不見了,程序又變回了Java泛型出現(xiàn)之前的寫法嫉柴,泛型類型都變回了原生類型厌杜。
interface Comparable<A> {
public int compareTo(A that);
}
public final class NumericValue implements Comparable<NumericValue> {
private byte value;
public NumericValue(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
public int compareTo(NumericValue that) {
return this.value - that.value;
}
}
反編譯后
interface Comparable {
public int compareTo( Object that);//最頂級的父類型替換
}
public final class NumericValue
implements Comparable
{
public NumericValue(byte value)
{
this.value = value;
}
public byte getValue()
{
return value;
}
public int compareTo(NumericValue that)
{
return value - that.value;
}
public volatile int compareTo(Object obj)
{
return compareTo((NumericValue)obj);
}
private byte value;
}
public class Collections {
public static <A extends Comparable<A>> A max(Collection<A> xs) {
Iterator<A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0)
w = x;
}
return w;
}
}
反編譯后
public class Collections
{
public Collections()
{
}
public static Comparable max(Collection xs)
{
Iterator xi = xs.iterator();
Comparable w = (Comparable)xi.next();
while(xi.hasNext())
{
Comparable x = (Comparable)xi.next();
if(w.compareTo(x) < 0)
w = x;
}
return w;
}
}
第2個泛型類Comparable <A>
擦除后 A被替換為最左邊界Object
。Comparable<NumericValue>
的類型參數(shù)NumericValue
被擦除掉计螺,但是這直 接導(dǎo)致NumericValue
沒有實現(xiàn)接口Comparable的compareTo(Object that)
方法夯尽,于是編譯器充當好人,添加了一個橋接方法登馒。
第3個示例中限定了類型參數(shù)的邊界<A extends Comparable<A>>A
匙握,A必須為Comparable<A>
的子類,按照類型擦除的過程谊娇,先講所有的類型參數(shù) ti換為最左邊界Comparable<A>
肺孤,然后去掉參數(shù)類型A
罗晕,得到最終的擦除后結(jié)果。
<mark>移除所有的類型參數(shù)赠堵。將所有的泛型參數(shù)用其最左邊界(最頂級的父類型)類型替換小渊。</mark>
泛型帶來的問題
重載
public class GenericTypes {
public static void method(List<String> list) {
System.out.println("invoke method(List<String> list)");
}
public static void method(List<Integer> list) {
System.out.println("invoke method(List<Integer> list)");
}
}
上面這段代碼,有兩個重載的函數(shù)茫叭,因為他們的參數(shù)類型不同酬屉,一個是List<String>
另一個是List<Integer>
,但是揍愁,這段代碼是編譯通不過的呐萨。因為我們前面講過,參數(shù)List<Integer>
和List<String>
編譯之后都被擦除了莽囤,變成了一樣的原生類型List谬擦,擦除動作導(dǎo)致這兩個方法的特征簽名變得一模一樣。
異常
1.不能拋出也不能捕獲泛型類的對象朽缎。事實上惨远,泛型類擴展Throwable都不合法。例如:下面的定義將不會通過編譯:
public class Problem<T> extends Exception{......}
為什么不能擴展Throwable话肖,因為異常都是在運行時捕獲和拋出的北秽,而在編譯的時候,泛型信息全都會被擦除掉最筒,那么贺氓,假設(shè)上面的編譯可行,那么床蜘,在看下面的定義:
try{
}catch(Problem<Integer> e1){
辙培。。
}catch(Problem<Number> e2){
...
}
類型信息被擦除后悄泥,那么兩個地方的catch都變?yōu)樵碱愋蚈bject虏冻,那么也就是說,這兩個地方的catch變的一模一樣,就相當于下面的這樣
try{
}catch(Problem<Object> e1){
弹囚。。
}catch(Problem<Object> e2){
...
//兩個一樣领曼。鸥鹉。
這個當然就是不行的。就好比庶骄,catch兩個一模一樣的普通異常毁渗,不能通過編譯一樣
try{
}catch(Exception e1){
。单刁。
}catch(Exception e2){//編譯錯誤
2.不能再catch子句中使用泛型變量
public static <T extends Throwable> void doWork(Class<T> t){
try{
...
}catch(T e){ //編譯錯誤
...
}
}
Cannot catch type parameters
不能捕獲類型參數(shù)
那么如果可以再catch子句中使用泛型變量灸异,那么,下面的定義呢:
public static <T extends Throwable> void doWork(Class<T> t){
try{
...
}catch(T e){ //編譯錯誤
...
}catch(IndexOutOfBounds e){
}
}
根據(jù)異常捕獲的原則,一定是子類在前面肺樟,父類在后面檐春,那么上面就違背了這個原則。即使你在使用該靜態(tài)方法的使用T是ArrayIndexOutofBounds么伯,在編譯之后還是會變成Throwable疟暖,ArrayIndexOutofBounds是IndexOutofBounds的子類,違背了異常捕獲的原則田柔。所以java為了避免這樣的情況俐巴,禁止在catch子句中使用泛型變量。
但是在異常聲明中可以使用類型變量硬爆。下面方法是合法的.
public static<T extends Throwable> void doWork(T t) throws T{
try{
t.getMessage();//類型變量
}catch(Throwable realCause){
t.initCause(realCause);//類型變量
throw t;
}
}
靜態(tài)變量
public class StaticTest{
public static void main(String[] args){
GT<Integer> gti = new GT<Integer>();
gti.var=1;
GT<String> gts = new GT<String>();
gts.var=2;
System.out.println(gti.var);
}
}
class GT<T>{
public static int var=0;
public void nothing(T x){}
}
答案是——2欣舵!由于經(jīng)過類型擦除,所有的泛型類實例都關(guān)聯(lián)到同一份字節(jié)碼上缀磕,泛型類的所有靜態(tài)變量是共享的缘圈。
區(qū)別
List<Object>
和原始類型List之間的區(qū)別?
原始類型List和帶參數(shù)類型List<Object>
之間的主要區(qū)別是虐骑,在編譯時編譯器不會對原始類型進行類型安全檢查准验,卻會對帶參數(shù)的類型進行檢查。
通過使用Object作為類型廷没,可以告知編譯器該方法可以接受任何類型的對象糊饱,比如String或Integer。
它們之間的第二點區(qū)別是颠黎,你可以把任何帶參數(shù)的類型傳遞給原始類型List另锋,但卻不能把List<String>
傳遞給接受 List<Object>
的方法,因為會產(chǎn)生編譯錯誤狭归。
List<?>
和List<Object>
之間的區(qū)別夭坪?
List<?>
是一個未知類型的List,而List<Object>
其實是任意類型的List过椎。你可以把List<String>
, List<Integer>
賦值給List<?>
室梅,卻不能把List<String>
賦值給 List<Object>
。
?和T有什么區(qū)別 疚宇?
T 代表一種類型亡鼠。
?是通配符,泛指所有類型敷待。
T t = (T) new ArrayList();
? s = (?) new ArrayList();//不可以
泛型數(shù)組
sun的說明文檔间涵,在java中是”不能創(chuàng)建一個確切的泛型類型的數(shù)組”的。
也就是說這個例子是不可以的:List<String>[] ls = new ArrayList<String>[10];
而使用通配符創(chuàng)建泛型數(shù)組是可以的:List<?>[] ls = new ArrayList<?>[10];
這樣也是可以的:List<String>[] ls = new ArrayList[10];
后面的自己看吧榜揖。
https://blog.csdn.net/s10461/article/details/53941091
參考文章
https://blog.csdn.net/s10461/article/details/53941091