2022最新Java泛型詳解(360度無死角介紹)
什么是泛型
Java泛型(generics)是JDK5中引入的一個新特性,泛型提供了 編譯時類型安全監(jiān)測機制,該機制允許我們在編譯時檢測到非法的類型數(shù)據(jù)結(jié)構(gòu)。泛型的本質(zhì)就是 參數(shù)化類型,也就是所操作的數(shù)據(jù)類型被指定為一個參數(shù)。
重點概念1:泛型的作用域是在編譯期間
List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){ System.out.println("泛型測試類型相同"); }
重點概念2:泛型主要作用是在編譯期間提供類型安全監(jiān)測機制
List arrayList = new ArrayList(); arrayList.add("aaaa"); arrayList.add(100); for(int i = 0; i< arrayList.size();i++){ String item = (String)arrayList.get(i); System.out.println("泛型測試item = " +item); }
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意類型,例子中首次添加了一個Sring類型,那么arrayList 再使用時都以String的方式使用,此時再次添加一個Integer類型的變量100,arrayList 只能嘗試將Integer類型的變量100轉(zhuǎn)為String,因此程序報錯;
為了解決類似這樣的問題,泛型應(yīng)運而生,我們將第一行聲明初始化list的代碼更改一下,編譯器會在編譯階段就能夠幫我們發(fā)現(xiàn)類似這樣的問題。
List<String> arrayList = new ArrayList<String>(); ... //arrayList.add(100); 在編譯階段,編譯器就會報錯
綜上可知:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型。
泛型的使用
泛型有三種使用方式,分別為:泛型類、泛型接口、泛型方法
泛型類
泛型類型用于類的定義中,被稱為泛型類。通過泛型可以完成對一組類的操作對外開放相同的接口。最典型的就是各種容器類,如:List、Set、Map。
//此處T可以隨便寫為任意標(biāo)識,常見的如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,K> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; } 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());
泛型接口
//定義一個泛型接口 public interface Generator<T> { public T next(); }
當(dāng)實現(xiàn)泛型接口的類,未傳入泛型實參時
/** * 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需將泛型的聲明也一起加到類中 * 即:class FruitGenerator<T> implements Generator<T>{ * 如果不聲明泛型,如:class FruitGenerator implements Generator<T>,編譯器會報錯:"Unknown class" */ class FruitGenerator<T> implements Generator<T>{ @Override public T next() { return null; } }
當(dāng)實現(xiàn)泛型接口的類,傳入泛型實參時:
/** * 傳入泛型實參時: * 定義一個生產(chǎn)器實現(xiàn)這個接口,雖然我們只創(chuàng)建了一個泛型接口Generator<T> * 但是我們可以為T傳入無數(shù)個實參,形成無數(shù)種類型的Generator接口。 * 在實現(xiàn)類實現(xiàn)泛型接口時,如已將泛型類型傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型 * 即:Generator<T>,public T next();中的的T都要替換成傳入的String類型。 */ 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)用方法的時候指明泛型的具體類型 。
例如上述Generic類中兩個方法
public T getKey(){ return key; }
雖然在方法中使用了泛型,但是這并不是一個泛型方法。這只是類中一個普通的成員方法,只不過他的返回值是在聲明泛型類已經(jīng)聲明過的泛型。所以在這個方法中才可以繼續(xù)使用 T 這個泛型。
public <T,K> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
這才是一個真正的泛型方法。 首先在public與返回值之間的必不可少,這表明這是一個泛型方法,并且聲明了一個泛型T這個T可以,出現(xiàn)在這個泛型方法的任意位置,泛型的數(shù)量也可以為任意多個
泛型類中的泛型方法
public class GenericFruit { class Fruit{ @Override public String toString() { return "fruit"; } } class Apple extends Fruit{ @Override public String toString() { return "apple"; } } class Person{ @Override public String toString() { return "Person"; } } class GenerateTest<T>{ public void show_1(T t){ System.out.println(t.toString()); } //在泛型類中聲明了一個泛型方法,使用泛型E,這種泛型E可以為任意類型。可以類型與T相同,也可以不同。 //由于泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中并未聲明泛型,編譯器也能夠正確識別泛型方法中識別的泛型。 public <E> void show_3(E t){ System.out.println(t.toString()); } //在泛型類中聲明了一個泛型方法,使用泛型T,注意這個T是一種全新的類型,可以與泛型類中聲明的T不是同一種類型。 public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args) { Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple是Fruit的子類,所以這里可以 generateTest.show_1(apple); //編譯器會報錯,因為泛型類型實參指定的是Fruit,而傳入的實參類是Person //generateTest.show_1(person); //使用這兩個方法都可以成功 generateTest.show_2(apple); generateTest.show_2(person); //使用這兩個方法也都可以成功 generateTest.show_3(apple); generateTest.show_3(person); } }
泛型通配符
Ingeter是Number的一個子類,Generic與Generic實際上是相同的一種基本類型。那么問題來了,在使用Generic作為形參的方法中,能否使用Generic的實例傳入呢?在邏輯上類似于Generic和Generic是否可以看成具有父子關(guān)系的泛型類型呢?
為了弄清楚這個問題,我們使用Generic這個泛型類繼續(xù)看下面的例子:
public void showKeyValue1(Generic<Number> obj){ Log.d("泛型測試","key value is " + obj.getKey()); }
Generic<Integer> gInteger = new Generic<Integer>(123); Generic<Number> gNumber = new Generic<Number>(456); showKeyValue(gNumber); showKeyValue(gInteger);
showKeyValue(gInteger);這個方法編譯器會為我們報錯:
Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
而且如果我們用重載的思維再定義一個Generic<java.lang.Integer>類型的方法
public static void showKeyValue1(Generic<Integer> obj){ System.out.println("泛型測試,key value is " + obj.getKey()); System.out.println("泛型測試類型相同"); } public static void showKeyValue1(Generic<Integer> obj){ System.out.println("泛型測試,key value is " + obj.getKey()); System.out.println("泛型測試類型相同"); }
會報錯重載異常
'showKeyValue1(Generic<Integer>)' is already defined in 'com.wzh.demo.test.FruitGenerator'
這其實是印證了上述的結(jié)論:泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型,也就是Generic<java.lang.Integer>和Generic<java.lang.Number>本質(zhì)都是Generic,但編譯期間方法入?yún)⒈仨氈付ǖ姆盒皖?,因?strong>同一種泛型可以對應(yīng)多個版本(因為參數(shù)類型是不確定的),不同版本的泛型類實例是不兼容的。
解決方案,使用泛型通配符?
public void showKeyValue1(Generic<?> obj){ Log.d("泛型測試","key value is " + obj.getKey()); }
類型通配符一般是使用?代替具體的類型實參,注意此處**’?’是類型實參,而不是類型形參** ,再直白點的意思就是,此處的?和Number、String、Integer一樣都是一種實際的類型,可以把?看成所有類型的父類。是一種真實的類型。當(dāng)具體類型不確定的時候。那么可以用 ? 通配符來表未知類型。
通配符上限
//結(jié)構(gòu) public class XxxClass<T extend XxxClass>
//案例 public void showKeyValue1(Generic<? extend Number> obj){ Log.d("泛型測試","key value is " + obj.getKey()); }
此時限定傳參的泛型類只能是Number或者Number的子類
通配符下限
//結(jié)構(gòu) public class XxxClass<T super XxxClass>
//案例 public void showKeyValue1(Generic<? super Number> obj){ Log.d("泛型測試","key value is " + obj.getKey()); }
此時限定傳參的泛型類只能是Number或者Number的父類
類型擦除
public class Erasure<T>{ //key這個成員變量的類型為T,T的類型由外部指定 private T key; public Erasure(T key) { //泛型構(gòu)造方法形參key的類型也為T,T的類型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值類型為T,T的類型由外部指定 return key; } public <T extends List> T show(T t){ //泛型方法getKey的返回值類型為T,T的類型由外部指定 return t; } }
我們從程序運行期間來看
public static void main(String[] args) { Erasure<Number> gNumber = new Erasure<Number>(456); Class<? extends Erasure> aClass = gNumber.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField.getName() + ":" +declaredField.getType().getSimpleName()); } }
結(jié)果
key:Object
如果我們給泛型加一個類型通配符上限
public class Erasure<T extends Number>{....}
那么,打印結(jié)果就是
key:Number
且對應(yīng)的方法
如果是接口類型的泛型
interface Info<T> { public T info(T t); } public class InfoImpl implements Info<Integer>{ @Override public Integer info(Integer var) { return var; } public static void main(String[] args) { Class<InfoImpl> infoClass = InfoImpl.class; Method[] declaredMethods = infoClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName()); } } }
打印結(jié)果
main:void
info:Integer
info:Object
個人備注一個疑問點,如上圖所示類型擦除后會生成一個Integer和Object類型的info方法,但為何我通過反射調(diào)用Object類型的info方法時候會有報錯呢?
public class InfoImpl implements Info<Integer>{ @Override public Integer info(Integer var) { return var; } public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException { Class<InfoImpl> infoClass = InfoImpl.class; Method[] declaredMethods = infoClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("methodNameAndType:" +declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName()); if(declaredMethod.getReturnType().getSimpleName().equals("Object")){ InfoImpl info = infoClass.newInstance(); // System.out.println("methodName:" +declaredMethod.getName()); Arrays.stream(declaredMethod.getParameterTypes()).forEach(param -> { System.out.println("param:" + param); }); System.out.println("method return :" +declaredMethod.getReturnType()); String str= "abc"; Object invoke = declaredMethod.invoke(info, str); System.out.println(invoke); } } } }
易混點強調(diào):由于泛型擦除機制,T僅僅是當(dāng)做一種符號,即占位符去使用,沒有實際意義,所以如果你試圖同T t = new T();實例化T類型的對象是通不過編譯的
class GenericsA<T> { T t = new T(); // Error }
但是我們可以通過反射來完成這一需求
public T createT(Class<T> tClass) throws IllegalAccessException, InstantiationException { return tClass.newInstance(); }
泛型與數(shù)組
可以聲明帶泛型的數(shù)據(jù)引用,但不能直接創(chuàng)建帶泛型的數(shù)組對象
小總結(jié)
泛型的作用域:編譯期間;
泛型的作用:
- 編譯時提供類型安全監(jiān)測機制,最典型的就是各種容器類如:List、Set、Map,通過泛型在編譯期間限制添加元素的類型
- 增強代碼規(guī)范性&復(fù)用性,例如設(shè)計模式模板方法模式結(jié)合泛型來使用,在模板方法中使用泛型,可以增強模板方法的復(fù)用性
泛型的類型問題:
- 泛型類型在邏輯上看以看成是多個不同的類型,實際上都是相同的基本類型,可以理解為泛型就是一種作用在編譯期的占位符,過了編譯期,運行期的類型擦除機制會完全擦除泛型類的影響
- 同一種泛型可以對應(yīng)多個版本(因為參數(shù)類型是不確定的),不同版本的泛型類實例是不兼容的,參考以下報錯來理解
Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
但我們可以通過泛型通配符Generic<?>
泛型類對比泛型方法
泛型類是在實例化類的時候指明泛型的具體類型;泛型方法是在調(diào)用方法的時候指明泛型的具體類型
到此這篇關(guān)于java泛型360度無死角詳細(xì)講解的文章就介紹到這了,更多相關(guān)java泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用try-with-resource的輸入輸出流自動關(guān)閉
這篇文章主要介紹了使用try-with-resource的輸入輸出流自動關(guān)閉方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07JavaBean valication驗證實現(xiàn)方法示例
這篇文章主要介紹了JavaBean valication驗證實現(xiàn)方法,結(jié)合實例形式分析了JavaBean valication驗證相關(guān)概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-03-03SpringBoot詳解shiro過濾器與權(quán)限控制
當(dāng)shiro被運用到web項目時,shiro會自動創(chuàng)建一些默認(rèn)的過濾器對客戶端請求進行過濾。比如身份驗證、授權(quán)的相關(guān)的,這篇文章主要介紹了shiro過濾器與權(quán)限控制2022-07-07關(guān)于idea2020.3升級lombok不能使用的問題
這篇文章主要介紹了關(guān)于idea2020.3升級lombok不能使用的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12idea啟動springboot報錯: 找不到或無法加載主類問題
這篇文章主要介紹了idea啟動springboot報錯: 找不到或無法加載主類問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12springboot redis使用lettuce配置多數(shù)據(jù)源的實現(xiàn)
這篇文章主要介紹了springboot redis使用lettuce配置多數(shù)據(jù)源的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Java實戰(zhàn)網(wǎng)上電子書城的實現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+JSP+maven+Mysql實現(xiàn)一個網(wǎng)上電子書城,大家可以在過程中查缺補漏,提升水平2022-01-01