java泛型基本知識(shí)和通用方法
一、泛型簡(jiǎn)介
1.引入泛型的目的
了解引入泛型的動(dòng)機(jī),就先從語(yǔ)法糖開(kāi)始了解。
語(yǔ)法糖
語(yǔ)法糖(Syntactic Sugar),也稱(chēng)糖衣語(yǔ)法,是由英國(guó)計(jì)算機(jī)學(xué)家Peter.J.Landin發(fā)明的一個(gè)術(shù)語(yǔ),指在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒(méi)有影響,但是更方便程序員使用。Java中最常用的語(yǔ)法糖主要有泛型、變長(zhǎng)參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類(lèi)等。虛擬機(jī)并不支持這些語(yǔ)法,它們?cè)诰幾g階段就被還原回了簡(jiǎn)單的基礎(chǔ)語(yǔ)法結(jié)構(gòu),這個(gè)過(guò)程成為解語(yǔ)法糖。
泛型的目的: Java 泛型就是把一種語(yǔ)法糖,通過(guò)泛型使得在編譯階段完成一些類(lèi)型轉(zhuǎn)換的工作,避免在運(yùn)行時(shí)強(qiáng)制類(lèi)型轉(zhuǎn)換而出現(xiàn)ClassCastException,即類(lèi)型轉(zhuǎn)換異常。
2.泛型初探
JDK 1.5 時(shí)才增加了泛型,并在很大程度上都是方便集合的使用,使其能夠記住其元素的數(shù)據(jù)類(lèi)型。
在泛型(Generic type或Generics)出現(xiàn)之前,是這么寫(xiě)代碼的:
public static void main(String[] args) { List list = new ArrayList(); list.add("123"); list.add("456"); System.out.println((String)list.get(0)); }
當(dāng)然這是完全允許的,因?yàn)長(zhǎng)ist里面的內(nèi)容是Object類(lèi)型的,自然任何對(duì)象類(lèi)型都可以放入、都可以取出,但是這么寫(xiě)會(huì)有兩個(gè)問(wèn)題:
1、當(dāng)一個(gè)對(duì)象放入集合時(shí),集合不會(huì)記住此對(duì)象的類(lèi)型,當(dāng)再次從集合中取出此對(duì)象時(shí),該對(duì)象的編譯類(lèi)型變成了Object。
2、運(yùn)行時(shí)需要人為地強(qiáng)制轉(zhuǎn)換類(lèi)型到具體目標(biāo),實(shí)際的程序絕不會(huì)這么簡(jiǎn)單,一個(gè)不小心就會(huì)出現(xiàn)java.lang.ClassCastException。
所以,泛型出現(xiàn)之后,上面的代碼就改成了大家都熟知的寫(xiě)法:
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("123"); list.add("456"); System.out.println(list.get(0)); }
這就是泛型。泛型是對(duì)Java語(yǔ)言類(lèi)型系統(tǒng)的一種擴(kuò)展,有點(diǎn)類(lèi)似于C++的模板,可以把類(lèi)型參數(shù)看作是使用參數(shù)化類(lèi)型時(shí)指定的類(lèi)型的一個(gè)占位符。引入泛型,是對(duì)Java語(yǔ)言一個(gè)較大的功能增強(qiáng),帶來(lái)了很多的好處。
3.泛型的好處
①類(lèi)型安全。類(lèi)型錯(cuò)誤現(xiàn)在在編譯期間就被捕獲到了,而不是在運(yùn)行時(shí)當(dāng)作java.lang.ClassCastException展示出來(lái),將類(lèi)型檢查從運(yùn)行時(shí)挪到編譯時(shí)有助于開(kāi)發(fā)者更容易找到錯(cuò)誤,并提高程序的可靠性。
②消除了代碼中許多的強(qiáng)制類(lèi)型轉(zhuǎn)換,增強(qiáng)了代碼的可讀性。
③為較大的優(yōu)化帶來(lái)了可能。
二、泛型的使用
1.泛型類(lèi)和泛型接口
下面是JDK 1.5 以后,List接口,以及ArrayList類(lèi)的代碼片段。
//定義接口時(shí)指定了一個(gè)類(lèi)型形參,該形參名為E public interface List<E> extends Collection<E> { //在該接口里,E可以作為類(lèi)型使用 public E get(int index) {} public void add(E e) {} } //定義類(lèi)時(shí)指定了一個(gè)類(lèi)型形參,該形參名為E public class ArrayList<E> extends AbstractList<E> implements List<E>{ //在該類(lèi)里,E可以作為類(lèi)型使用 public void set(E e) { ....................... } }
這就是泛型的實(shí)質(zhì):允許在定義接口、類(lèi)時(shí)聲明類(lèi)型形參,類(lèi)型形參在整個(gè)接口、類(lèi)體內(nèi)可當(dāng)成類(lèi)型使用,幾乎所有可使用普通類(lèi)型的地方都可以使用這種類(lèi)型形參。
下面具體講解泛型類(lèi)的使用。泛型接口的使用與泛型類(lèi)幾乎相同,可以比對(duì)自行學(xué)習(xí)。
泛型類(lèi)
定義一個(gè)容器類(lèi),存放鍵值對(duì)key-value,鍵值對(duì)的類(lèi)型不確定,可以使用泛型來(lái)定義,分別指定為K和V
public class Container<K, V> { private K key; private V value; public Container(K k, V v) { key = k; value = v; } public K getkey() { return key; } public V getValue() { return value; } public void setKey() { this.key = key; } public void setValue() { this.value = value; } }
在使用Container類(lèi)時(shí),只需要指定K,V的具體類(lèi)型即可,從而創(chuàng)建出邏輯上不同的Container實(shí)例,用來(lái)存放不同的數(shù)據(jù)類(lèi)型。
public static void main(String[] args){ Container<String,String> c1=new Container<String ,String>("name","hello"); Container<String,Integer> c2=new Container<String,Integer>("age",22); Container<Double,Double> c3=new Container<Double,Double>(1.1,1.3); System.out.println(c1.getKey() + " : " + c1.getValue()); System.out.println(c2.getKey() + " : " + c2.getValue()); System.out.println(c3.getKey() + " : " + c3.getValue()); }
在JDK 1.7 增加了泛型的“菱形”語(yǔ)法:Java允許在構(gòu)造器后不需要帶完成的泛型信息,只要給出一對(duì)尖括號(hào)(<>)即可,Java可以推斷尖括號(hào)里應(yīng)該是什么泛型信息。
如下所示:
Container<String,String> c1=new Container<>("name","hello"); Container<String,Integer> c2=new Container<>("age",22);
泛型類(lèi)派生子類(lèi)
當(dāng)創(chuàng)建了帶泛型聲明的接口、父類(lèi)之后,可以為該接口創(chuàng)建實(shí)現(xiàn)類(lèi),或者從該父類(lèi)派生子類(lèi),需要注意:使用這些接口、父類(lèi)派生子類(lèi)時(shí)不能再包含類(lèi)型形參,需要傳入具體的類(lèi)型。錯(cuò)誤的方式:
public class A extends Container<K, V>{}
正確的方式:
public class A extends Container<Integer, String>{}
也可以不指定具體的類(lèi)型,如下:
public class A extends Container{}
此時(shí)系統(tǒng)會(huì)把K,V形參當(dāng)成Object類(lèi)型處理。
2.泛型的方法
前面在介紹泛型類(lèi)和泛型接口中提到,可以在泛型類(lèi)、泛型接口的方法中,把泛型中聲明的類(lèi)型形參當(dāng)成普通類(lèi)型使用。 如下面的方式:
public class Container<K, V> { ........................ public K getkey() { return key; } public void setKey() { this.key = key; } .................... }
但在另外一些情況下,在類(lèi)、接口中沒(méi)有使用泛型時(shí),定義方法時(shí)想定義類(lèi)型形參,就會(huì)使用泛型方法。如下方式:
public class Main{ public static <T> void out(T t){ System.out.println(t); } public static void main(String[] args){ out("hansheng"); out(123); } }
所謂泛型方法,就是在聲明方法時(shí)定義一個(gè)或多個(gè)類(lèi)型形參。泛型方法的用法格式如下:
修飾符<T, S> 返回值類(lèi)型 方法名(形參列表) { 方法體 }
注意:方法聲明中定義的形參只能在該方法里使用,而接口、類(lèi)聲明中定義的類(lèi)型形參則可以在整個(gè)接口、類(lèi)中使用。
class Demo{ public <T> T fun(T t){ // 可以接收任意類(lèi)型的數(shù)據(jù) return t ; // 直接把參數(shù)返回 } } public class GenericsDemo26{ public static void main(String args[]){ Demo d = new Demo() ; // 實(shí)例化Demo對(duì)象 String str = d.fun("湯姆") ; // 傳遞字符串 int i = d.fun(30) ; // 傳遞數(shù)字,自動(dòng)裝箱 System.out.println(str) ; // 輸出內(nèi)容 System.out.println(i) ; // 輸出內(nèi)容 } };
當(dāng)調(diào)用fun()
方法時(shí),根據(jù)傳入的實(shí)際對(duì)象,編譯器就會(huì)判斷出類(lèi)型形參T所代表的實(shí)際類(lèi)型。
3.泛型構(gòu)造器
正如泛型方法允許在方法簽名中聲明類(lèi)型形參一樣,Java也允許在構(gòu)造器簽名中聲明類(lèi)型形參,這樣就產(chǎn)生了所謂的泛型構(gòu)造器。
和使用普通泛型方法一樣沒(méi)區(qū)別,一種是顯式指定泛型參數(shù),另一種是隱式推斷,如果是顯式指定則以顯式指定的類(lèi)型參數(shù)為準(zhǔn),如果傳入的參數(shù)的類(lèi)型和指定的類(lèi)型實(shí)參不符,將會(huì)編譯報(bào)錯(cuò)。
public class Person { public <T> Person(T t) { System.out.println(t); } }
public static void main(String[] args){ //隱式 new Person(22); //顯示 new<String>Person("hello"); }
這里唯一需要特殊注明的就是,如果構(gòu)造器是泛型構(gòu)造器,同時(shí)該類(lèi)也是一個(gè)泛型類(lèi)的情況下應(yīng)該如何使用泛型構(gòu)造器:
因?yàn)榉盒蜆?gòu)造器可以顯式指定自己的類(lèi)型參數(shù)(需要用到菱形,放在構(gòu)造器之前),而泛型類(lèi)自己的類(lèi)型實(shí)參也需要指定(菱形放在構(gòu)造器之后),這就同時(shí)出現(xiàn)了兩個(gè)菱形了,這就會(huì)有一些小問(wèn)題,具體用法再這里總結(jié)一下。
以下面這個(gè)例子為代表
public class Person<E> { public <T> Person(T t) { System.out.println(t); } }
這種用法:Person<String> a = new <Integer>Person<>(15); 這種語(yǔ)法不允許,會(huì)直接編譯報(bào)錯(cuò)!
三、類(lèi)型通配符
顧名思義就是匹配任意類(lèi)型的類(lèi)型實(shí)參。
類(lèi)型通配符是一個(gè)問(wèn)號(hào)(?),將一個(gè)問(wèn)號(hào)作為類(lèi)型實(shí)參傳給List集合,寫(xiě)作:List<?>(意思是元素類(lèi)型未知的List)。這個(gè)問(wèn)號(hào)(?)被成為通配符,它的元素類(lèi)型可以匹配任何類(lèi)型
public void test(List<?> c){ for(int i =0;i<c.size();i++){ System.out.println(c.get(i)); } }
現(xiàn)在可以傳入任何類(lèi)型的List來(lái)調(diào)用test()方法,程序依然可以訪問(wèn)集合c中的元素,其類(lèi)型是Object。
List<?> c = new ArrayList<String>(); //編譯器報(bào)錯(cuò) c.add(new Object());
但是并不能把元素加入到其中。因?yàn)槌绦驘o(wú)法確定c集合中元素的類(lèi)型,所以不能向其添加對(duì)象。下面就該引入帶限通配符,來(lái)確定集合元素中的類(lèi)型。
帶限通配符
簡(jiǎn)單來(lái)講,使用通配符的目的是來(lái)限制泛型的類(lèi)型參數(shù)的類(lèi)型,使其滿足某種條件,固定為某些類(lèi)。
主要分為兩類(lèi)即:上限通配符和下限通配符。
1.上限通配符
如果想限制使用泛型類(lèi)別時(shí),只能用某個(gè)特定類(lèi)型或者是其子類(lèi)型才能實(shí)例化該類(lèi)型時(shí),可以在定義類(lèi)型時(shí),使用extends關(guān)鍵字指定這個(gè)類(lèi)型必須是繼承某個(gè)類(lèi),或者實(shí)現(xiàn)某個(gè)接口,也可以是這個(gè)類(lèi)或接口本身。。
它表示集合中的所有元素都是Shape類(lèi)型或者其子類(lèi) List<? extends Shape>
這就是所謂的上限通配符,使用關(guān)鍵字extends來(lái)實(shí)現(xiàn),實(shí)例化時(shí),指定類(lèi)型實(shí)參只能是extends后類(lèi)型的子類(lèi)或其本身。例如:
//Circle是其子類(lèi) List<? extends Shape> list = new ArrayList<Circle>();
這樣就確定集合中元素的類(lèi)型,雖然不確定具體的類(lèi)型,但最起碼知道其父類(lèi)。然后進(jìn)行其他操作。
2.下限通配符
如果想限制使用泛型類(lèi)別時(shí),只能用某個(gè)特定類(lèi)型或者是其父類(lèi)型才能實(shí)例化該類(lèi)型時(shí),可以在定義類(lèi)型時(shí),使用super關(guān)鍵字指定這個(gè)類(lèi)型必須是是某個(gè)類(lèi)的父類(lèi),或者是某個(gè)接口的父接口,也可以是這個(gè)類(lèi)或接口本身。
它表示集合中的所有元素都是Circle類(lèi)型或者其父類(lèi) List<? super Circle>
這就是所謂的下限通配符,使用關(guān)鍵字super來(lái)實(shí)現(xiàn),實(shí)例化時(shí),指定類(lèi)型實(shí)參只能是extends后類(lèi)型的子類(lèi)或其本身。例如:
它表示集合中的所有元素都是Circle類(lèi)型或者其父類(lèi) List<? super Circle>
四、類(lèi)型擦除
Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String>().getClass(); System.out.println(c1==c2);
程序輸出:
true。
這是因?yàn)椴还転榉盒偷念?lèi)型形參傳入哪一種類(lèi)型實(shí)參,對(duì)于Java來(lái)說(shuō),它們依然被當(dāng)成同一類(lèi)處理,在內(nèi)存中也只占用一塊內(nèi)存空間。從Java泛型這一概念提出的目的來(lái)看,其只是作用于代碼編譯階段,在編譯過(guò)程中,對(duì)于正確檢驗(yàn)泛型結(jié)果后,會(huì)將泛型的相關(guān)信息擦出,也就是說(shuō),成功編譯過(guò)后的class文件中是不包含任何泛型信息的。泛型信息不會(huì)進(jìn)入到運(yùn)行時(shí)階段。
在靜態(tài)方法、靜態(tài)初始化塊或者靜態(tài)變量的聲明和初始化中不允許使用類(lèi)型形參。由于系統(tǒng)中并不會(huì)真正生成泛型類(lèi),所以instanceof運(yùn)算符后不能使用泛型類(lèi)。
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- Java:泛型知識(shí)知多少
- Java泛型枚舉Annotation接口詳細(xì)解讀與Eclipse發(fā)展
- Java的類(lèi)型擦除式泛型詳解
- 一篇文章帶你了解Java泛型的super和extends
- 一篇文章帶你入門(mén)java泛型
- Java不能真正泛型的原因是什么?
- 深入了解Java核心類(lèi)庫(kù)--泛型類(lèi)
- Java泛型的類(lèi)型擦除示例詳解
- Java基礎(chǔ)之java泛型通配符詳解
- Java 泛型詳解(超詳細(xì)的java泛型方法解析)
- java泛型的局限探究及知識(shí)點(diǎn)總結(jié)
- Java泛型機(jī)制與反射原理相關(guān)知識(shí)總結(jié)
- Java中的泛型
相關(guān)文章
springmvc+mybatis 做分頁(yè)sql 語(yǔ)句實(shí)例代碼
本文通過(guò)一段實(shí)例代碼給大家介紹了springmvc+mybatis 做分頁(yè)sql 語(yǔ)句的方法,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-07-07JAVA實(shí)現(xiàn)讀取txt文件內(nèi)容的方法
本篇文章主要介紹了JAVA實(shí)現(xiàn)讀取txt文件內(nèi)容的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01SpringBoot參數(shù)校驗(yàn)的最佳實(shí)戰(zhàn)教程
開(kāi)發(fā)過(guò)程中,后臺(tái)的參數(shù)校驗(yàn)是必不可少的,下面這篇文章主要給大家介紹了關(guān)于SpringBoot參數(shù)校驗(yàn)的最佳實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Java精品項(xiàng)目瑞吉外賣(mài)之后端登錄功能篇
這篇文章主要為大家詳細(xì)介紹了java精品項(xiàng)目-瑞吉外賣(mài)訂餐系統(tǒng),此項(xiàng)目過(guò)大,分為多章獨(dú)立講解,本篇內(nèi)容為后端登錄功能的實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Java實(shí)戰(zhàn)入門(mén)之雙色球彩票小游戲
這篇文章主要介紹了Java實(shí)戰(zhàn)入門(mén)之雙色球彩票,文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04關(guān)于泛型擦除問(wèn)題的解決--Mybatis查詢類(lèi)型轉(zhuǎn)換
這篇文章主要介紹了關(guān)于泛型擦除問(wèn)題的解決--Mybatis查詢類(lèi)型轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08Netty中的DelimiterBasedFrameDecoder使用方法詳解
這篇文章主要介紹了Netty中的DelimiterBasedFrameDecoder使用方法詳解,DelimiterBasedFrameDecoder與LineBasedFrameDecoder類(lèi)似,只不過(guò)更加通用,允許我們指定任意特殊字符作為分隔符,我們還可以同時(shí)指定多個(gè)分隔符,需要的朋友可以參考下2023-12-12