Java通俗易懂講解泛型
1.什么是泛型
泛型是在JDK1.5引入的新的語(yǔ)法,通俗講,泛型:就是適用于許多許多類型。從代碼上講,就是對(duì)類型實(shí)現(xiàn)了參數(shù)化。
看到標(biāo)紅的這句話,有些兄弟可能會(huì)有疑惑,我們以前傳參,傳得是一個(gè)整形數(shù)據(jù),傳得是一個(gè)引用,我從來(lái)沒(méi)傳過(guò)類型嘛,類型難道還能作為參數(shù)傳遞???對(duì),沒(méi)錯(cuò),接下來(lái),我們就來(lái)看一下類型作為參數(shù)傳遞的實(shí)現(xiàn)方式
2.引出泛型
我們以前學(xué)過(guò)繼承和多態(tài),我們知道所有類的父類默認(rèn)是Object類,我們現(xiàn)在目標(biāo)是:數(shù)組中可以存放任何類型的數(shù)據(jù),這時(shí)候我們就可以new一個(gè)Object類型的數(shù)組,,,
class MyArray { public Object[] array = new Object[10]; public Object getPos(int pos) { return array[pos]; } public void setPos(int pos, Object value) { array[pos] = value; } } public class TestDemo { public static void main(String[] args) { MyArray myArray = new MyArray(); myArray.setPos(0,1); myArray.setPos(1,"hello"); } }
當(dāng)我們寫出這樣一個(gè)代碼的時(shí)候,我們發(fā)現(xiàn)數(shù)組中什么元素都能放了,可以放整形數(shù)據(jù),可以放字符串,還可以放小數(shù),這里面能放的東西太多了,你能知道某個(gè)下標(biāo)的元素是什么樣的類型嗎??當(dāng)我們?nèi)≡氐臅r(shí)候,會(huì)發(fā)現(xiàn)你的代碼編譯都不能通過(guò),,,
為什么會(huì)出現(xiàn)這樣的情況呢,我相信大部分兄弟們都知道,我們觀察getPos的返回值是Object,也就意味著我們拿出來(lái)的可能是任意元素,但編譯器知道嗎???它只知道返回的是一個(gè)Object:這就很明顯了,父類給子類,,這不純純的向下轉(zhuǎn)型嗎,向下轉(zhuǎn)型就必須要強(qiáng)轉(zhuǎn)類型,否則你的代碼就不可能通過(guò)。
那么問(wèn)題又來(lái)了,,我這里只是放了兩個(gè)元素,肉眼還能看的過(guò)來(lái),但是這里面的類型時(shí)刻在發(fā)生改變,就算你知道類型,你每次都需要強(qiáng)轉(zhuǎn)嗎,我是動(dòng)態(tài)的啊,我代碼是寫死的啊,所以這個(gè)地方如果用Object去做的話,會(huì)顯得格外麻煩,而且局限性非常的大
我們現(xiàn)在的困境有兩個(gè):
1.任何類型都可以放,不好控制,,,
2.每次取元素的時(shí)候,都得進(jìn)行強(qiáng)制類型轉(zhuǎn)換,,,
這時(shí)候我們得搞一手希望工程,我們的希望是:
1.我們能不能自己指定類型
2.我們能不能,不再進(jìn)行類型轉(zhuǎn)換呀,能不能把這一步省略呀,兄弟!?。?/p>
為了解決這一問(wèn)題,我們java就引入了這個(gè)東西----》泛型
那到底怎么指定類型呢,來(lái)看代碼:
class MyArray <T>{ //public Object[] array = new Object[10]; public T[] array = (T[]) new Object[10];//這樣寫也不好,待會(huì)說(shuō)為什么?? public T getPos(int pos) { return array[pos]; } public void setPos(int pos, T value) { array[pos] = value; } } public class TestDemo { public static void main(String[] args) { //我指定放整形 MyArray<Integer> myArray = new MyArray<>(); //后面的尖括號(hào)里的類型可以省略,在實(shí)例化對(duì)象的時(shí)候根據(jù)前面可以做出判斷 //MyArray<Integer> myArray = new MyArray<Integer>(); myArray.setPos(0,1); myArray.setPos(1,2); Integer ret = myArray.getPos(1); System.out.println(ret); //我指定放字符串類型 MyArray<String> myArray2 = new MyArray<>(); myArray2.setPos(0,"abc"); myArray2.setPos(1,"bit"); String ret2 = myArray2.getPos(0); System.out.println(ret2); } }
這個(gè)代碼有幾個(gè)注意事項(xiàng):
1.尖括號(hào)<>里面只能放引用類型;
2.<T>是一個(gè)占位符,代表當(dāng)前類是一個(gè)泛型類;
3.MyArray<Integer> array = new MyArray<>();后面的尖括號(hào)可以省略Integer不寫,實(shí)例化對(duì)象的 時(shí)候編譯器根據(jù)前面能做出判斷。
看完上面的代碼,我相信兄弟們已經(jīng)明白是怎么一回事了吧
我們剛剛的希望工程也已經(jīng)實(shí)現(xiàn)了
1.<Integer>,指定當(dāng)前類中,使用的類型是Integer類型
2.泛型幫我在編譯期間做了2件事情(目前為止的優(yōu)點(diǎn)):
- 存放元素的時(shí)候,進(jìn)行類型的檢查
- 取元素的時(shí)候,幫我進(jìn)行類型的轉(zhuǎn)換
3.泛型類的語(yǔ)法
有了剛剛知識(shí)的鋪墊,這一塊咱就形式化一下啦,,當(dāng)然,語(yǔ)法還是很重要滴?。?/p>
語(yǔ)法:
泛型類<類型實(shí)參> 變量名; // 定義一個(gè)泛型類引用
new 泛型類<類型實(shí)參>(構(gòu)造方法實(shí)參); // 實(shí)例化一個(gè)泛型類對(duì)象
栗子:
MyArray<Integer> list = new MyArray<Integer>();
4.裸類型
說(shuō)明:
兄弟們,咱學(xué)了新東西,這舊東西,咱也得了解一下是不咯,,萬(wàn)一以后跟別人聊到了,自己好像從來(lái)沒(méi)聽過(guò)就尷尬了,,
裸類型是一個(gè)泛型類但沒(méi)有帶著類型實(shí)參,例如 MyArrayList 就是一個(gè)裸類型
MyArray list = new MyArray();
注意: 我們不要自己去使用裸類型,裸類型是為了兼容老版本的 API 保留的機(jī)制
5.泛型如何編譯的
5.1 擦除機(jī)制
我用的是Powershell窗口查看的字節(jié)碼文件,當(dāng)然用idea的兄弟們也可以去下載一個(gè)展示字節(jié)碼的插件,,接下來(lái)我們看看泛型到底是怎么編譯的,,javap -c查看字節(jié)碼
我們發(fā)現(xiàn)在編譯期間,所有的T都被擦除為了Object,那既然所有的T都變成了Object,那我們?yōu)槭惨付愋湍??注意這里不是編譯的時(shí)候替換,指定類型只是為了在編譯的時(shí)候幫我們進(jìn)行類型的檢查和轉(zhuǎn)換,并不是替換?。?!最終字節(jié)碼編譯完成的時(shí)候,所有的T變成了Object,而這就是我們Java當(dāng)中泛型所謂的擦除機(jī)制?。?!
提問(wèn):那既然T在編譯的時(shí)候被擦除為了Object,那為啥 T[] array = new T[] 會(huì)報(bào)錯(cuò)呢?
5.2會(huì)給你答案??????
5.2.泛型數(shù)組為什么不能實(shí)例化
先看一段代碼和運(yùn)行結(jié)果:
class MyArray<T> { public T[] array = (T[])new Object[10]; public T getPos(int pos) { return this.array[pos]; } public void setVal(int pos,T val) { this.array[pos] = val; } public T[] getArray() { return array; } } public class TestDemo { public static void main(String[] args) { MyArray<Integer> myArray1 = new MyArray<>(); Integer[] strings = myArray1.getArray();//這里運(yùn)行時(shí)會(huì)報(bào)錯(cuò) String[] strings1 = (String)myArray1.getArray();//這里編譯都不能通過(guò) } }
出現(xiàn)這種情況的原因何在呢?
這里最大的問(wèn)題就是數(shù)組,java中的數(shù)組是非常特殊的,它是在堆上分配內(nèi)存的,它有自己的一套特殊的機(jī)制,而T[] array = new T[] 這個(gè)數(shù)組它沒(méi)有指定具體的類型,肯定是不讓這樣做的;再者,get方法的返回類型是Object類型,如果用整形數(shù)組來(lái)接受,父類給子類,編譯器是直接不讓你做的。
那問(wèn)題來(lái)了???既然 T[] array = new T[] 這種寫法是錯(cuò)誤的,T[] array = (T[])new Object[10] 這種寫法又不好,那正確的寫法應(yīng)該是怎么的呢???
正確寫法:(了解即可)
/** * 通過(guò)反射創(chuàng)建指定類型的數(shù)組 */ public MyArray(Class<T> clazz, int capacity) { array = (T[]) Array.newInstance(clazz, capacity); }
看起來(lái)很復(fù)雜,對(duì)吧,但是沒(méi)關(guān)系,java官方都不這么用,所以我們也只需要了解即可。
我們可以看下ArrayList的源碼來(lái)確認(rèn)一下(以免存在忽悠的成份):
6.泛型的上界
什么叫泛型的上界呢??先給你們看一下語(yǔ)法:
class 泛型類名稱<類型形參 extends 類型邊界> {
...
}
這一模塊我將舉兩個(gè)栗子來(lái)和大家一起認(rèn)識(shí)或溫習(xí)一下這個(gè)知識(shí)點(diǎn),,,
簡(jiǎn)單示例:
class Test<T extends Number> { } public class TestDemo { public static void main(String[] args) { //Number的子類 Test<Integer> test = new Test<>(); Test<Double> test1 = new Test<>(); Test<Float> test2 = new Test<>(); //Number本身 Test<Number> test3 = new Test<>(); } }
<T extends Number>中的 extends可不敢理解為繼承喔,可以理解為T擦除為了Number的子類或自己本身,也就是說(shuō)在主函數(shù)中用這個(gè)類實(shí)例化的時(shí)候,可以指定的類型一定要 <= Number ,,差不多就這意思,,,簡(jiǎn)單的你們都能理解,我就不多廢話了,給你們來(lái)點(diǎn)稍微有難度的(我覺(jué)得難)
進(jìn)階示例:
class Alg<T extends Comparable<T>> { //如果不設(shè)置上界為Comparable,就用不了compareTo這個(gè)方法,因?yàn)門如果不指定的話, // 在編譯的時(shí)候被擦除成了Object,而Object并沒(méi)有實(shí)現(xiàn)這個(gè)接口 public T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(max.compareTo(array[i]) < 0) { max = array[i]; } } return max; } } class Student implements Comparable<Student> { public String name; public int age; public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } @Override public int compareTo(Student o) { return 0; } } public class TestDemo { public static void main(String[] args) { //指定類型為整形 Alg<Integer> alg = new Alg<>(); Integer[] array = {1,2,3,4,5}; Integer max = alg.findMax(array); System.out.println(max); //指定類型為Student Alg<Student> alg2 = new Alg<>(); Student[] students = new Student[3]; students[0] = new Student("毛毛",15); students[1] = new Student("吉吉",13); students[2] = new Student("毛二",16); Student student = alg2.findMax(students); System.out.println(student); } }
<T extends Comparable<T>>意為擦除到實(shí)現(xiàn)了這個(gè)接口,簡(jiǎn)而言之就是這里面接收的類型一定是實(shí)現(xiàn)了Comparable接口的,給你們證明一下,我那里面Student類型毋庸置疑,自己實(shí)現(xiàn)的Comparable接口,我們來(lái)看看Integer的源碼:
再者,我們觀察一下 Alg 這個(gè)類中的 findMax 方法,我們都知道引用類型比較大小要用比較器或者compareTo方法,如果 Student 這個(gè)類沒(méi)有實(shí)現(xiàn) Comparable 接口,我們?cè)趯懘a的時(shí)候,會(huì)發(fā)現(xiàn)點(diǎn)都點(diǎn)不出來(lái),比較一下,就通透了,,
沒(méi)有實(shí)現(xiàn)Comparable接口:
實(shí)現(xiàn)了Comparable接口:
當(dāng)然,又有人會(huì)問(wèn),我能不能不實(shí)例化這個(gè)方法,直接調(diào)用這個(gè)方法,這時(shí)候我們把這個(gè)方法搞成靜態(tài)的來(lái)看一下:
發(fā)現(xiàn)它報(bào)錯(cuò)了,,,別急哈,咱們一步一步是爪牙,,哦,不對(duì),是一步一步來(lái)揭開面紗,,
那如何將這個(gè)代碼改成正確的呢??細(xì)心的兄弟會(huì)發(fā)現(xiàn),既然這個(gè)方法是靜態(tài)的, 那我們只需要通過(guò)類名來(lái)方法,如果還按照之前的寫法,類后面跟一個(gè) <T>,那我們啥時(shí)候給它指定類型??不知道吧,這時(shí)候我們要將代碼改成這樣,在 static 的后面跟一個(gè) <T>,這樣你的代碼就不會(huì)報(bào)錯(cuò)了,,,
當(dāng)然,我們也可以指定邊界,這里跟類有著異曲同工之妙,就不細(xì)說(shuō)了,花兩秒鐘看一下代碼,你就能發(fā)出: 哦~~~~~的聲音,中國(guó)人不騙中國(guó)人
class Alg2 <T>{ public static<T extends Comparable> T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(max.compareTo(array[i]) < 0) { max = array[i]; } } return max; } }
最后,你們可能會(huì)問(wèn),那它既然有上界,那它有下界嗎??
那肯定是沒(méi)有的啊,咱學(xué)習(xí)編程多辛苦啊 ??!是不是兄弟們
7.通配符
兄弟們,最后兩個(gè)知識(shí)點(diǎn),咱泛型就告一段落了,,
什么是通配符??我還是換一行吧
?這個(gè)就是通配符,對(duì),沒(méi)錯(cuò),就是這個(gè)問(wèn)號(hào) "?"
7.1.通配符能用來(lái)干嘛
JAVA中引入通配符這一玩意是用來(lái)解決泛型無(wú)法協(xié)變這一問(wèn)題的,那什么是協(xié)變呢?
協(xié)變這東西怎么說(shuō)呢,類似于,繼承當(dāng)中的父類引用子類的對(duì)象,但是泛型中不支持協(xié)變類型,而JAVA的數(shù)組是支持的,數(shù)組中明確了它所允許存儲(chǔ)的對(duì)象類型,并且會(huì)在運(yùn)行時(shí)做類型檢查(這也是為什么不能創(chuàng)建泛型數(shù)組的原因,數(shù)組創(chuàng)建時(shí)必須知道確切類型)。
下面看具體代碼,看看是如何解決協(xié)變問(wèn)題的,,,
class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class TestDemo2 { public static void main(String[] args) { Message<Integer> message = new Message<>(); message.setMessage(1); fun(message);//編譯不通過(guò) } public static void fun(Message<String> temp) { System.out.println(temp.getMessage()); } }
首先,上面這一段代碼編譯是不能通過(guò)的,很明顯我 fun 方法中只能接收 String 類型的數(shù)據(jù),而你給我一個(gè)整形數(shù)據(jù),肯定會(huì)報(bào)錯(cuò)嗎,當(dāng)我把它改成這樣的時(shí)候,,你會(huì)發(fā)現(xiàn)代碼不報(bào)錯(cuò)了:
class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class TestDemo2 { public static void main(String[] args) { Message<Integer> message = new Message<>(); message.setMessage(1); fun(message); } public static void fun(Message<?> temp) { //temp.setMessage(1);//是不能往里面加元素的,所以源碼當(dāng)中通配符用的比較多 System.out.println(temp.getMessage()); } }
這里尖括號(hào)里面放?意味著可以接收任何類型,人家用戶你可不知道你這是什么類型,所以通配符在這一塊就起了非常大的作用;再者,既然可以接收所有類型,就不能讓用戶隨便修改。具體是怎么做到的呢?也就是說(shuō)當(dāng)你用通配符在函數(shù)中接收任意類型之后,你不能調(diào)用 set 方法往里面設(shè)值,你根本不知道你接收到的是什么類型,,,所以,我們也看到源碼當(dāng)中通配符用的比較多就是這么回事
7.2.通配符的上界(讀數(shù)據(jù))
通配符的上界,這東西跟剛剛的泛型上界很相似,有了剛剛的只是鋪墊,應(yīng)該不難理解了,,語(yǔ)法這一塊咱就不演示了
class Food {} class Fruit extends Food {} class Apple extends Fruit {} class Banana extends Fruit {} class Plate<T> { private T pear; public T getPlate() { return pear; } public void setPlate(T a) { this.pear = a; } } public class TestDemo3 { public static void main(String[] args) { Plate<Apple> plate = new Plate<>(); Plate<Banana> plate1 = new Plate<> (); fun1(plate); fun1(plate1); } public static void fun1(Plate<? extends Fruit> temp) { //temp.setPlate(new Apple());這里不知道temp引用的子類是誰(shuí),所以不能往里邊放元素 //可以接收元素,父類接收子類,頂多就是向上轉(zhuǎn)型 System.out.println(temp.getPlate()); //Fruit fruit = temp.getPlate(); } }
我們這里給了它香蕉/蘋果等一些水果類和 Fruit 這個(gè)父類,<? extends Fruit>這里可以接收任何Fruit 的子類類型,同理,既然可以接收任何類型,就不適合寫數(shù)據(jù),只適合讀數(shù)據(jù),,
7.3.通配符的下界(寫數(shù)據(jù))
通配符不同于泛型,它是有下界的,來(lái)看看它的下界,,,
語(yǔ)法大概就這樣子,簡(jiǎn)單了解一下:<? super 下界>---重點(diǎn)在代碼
public class TestDemo3 { public static void main(String[] args) { Plate<Fruit> plate = new Plate<>(); fun(plate); Plate<Food> plate2 = new Plate<>(); fun(plate2); Plate<Apple> plate1 = new Plate<>(); //fun(plate1); } public static void fun(Plate<? super Fruit> temp) { //寫數(shù)據(jù),但是不能寫父類 temp.setPlate(new Apple()); temp.setPlate(new Banana()); //編譯不通過(guò):Fruit的父類有很多,不一定就是某一個(gè) //temp.setPlate(new Food()); //不安全--它的父類有很多,不知道是哪一個(gè)父類發(fā)生了向下轉(zhuǎn)型 //Fruit fruit = (Fruit)temp.getPlate(); } }
通配符的下界呢,,我們可以看到它是用了一個(gè)關(guān)鍵字super,字面意思,大家都懂,就是說(shuō)能接收的類型至少是 Fruit ,以及 Fruit 的父類,所以,在讀寫數(shù)據(jù)這一塊,它就不同于別人,它只喜歡寫數(shù)據(jù),就像咱人一樣,總得有些偏執(zhí)的愛(ài)好,我就喜歡敲代碼,是吧,,,為啥不能讀數(shù)據(jù),這里還是得解釋一下,因?yàn)?Fruit 的父類太多了,你根本不知道讀誰(shuí)的數(shù)據(jù),再者就算你強(qiáng)轉(zhuǎn)類型,編譯器也會(huì)認(rèn)為這是不安全的,所以我們?cè)谧x數(shù)據(jù)和寫數(shù)據(jù)這一塊,?的上界和?的下界要搞清楚什么時(shí)候用哪一個(gè),,,
下圖能幫助你理解讀寫數(shù)據(jù),到底適合哪一個(gè)
本來(lái)還有一個(gè)裝箱和拆箱的類容要給兄弟們分享的,但是現(xiàn)在已經(jīng)凌晨?jī)牲c(diǎn)了(小命要緊,建議大家不要像我這樣,身體是內(nèi)卷的本錢,我只是一時(shí)興起,不敢瞞著兄弟們偷偷卷),小編我要為那些目前還在渴望這一塊知識(shí)的年輕小伙子著想,就把那些內(nèi)容單獨(dú)抽到下一期去了,,,下期見,兄弟們!?。?/p>
到此這篇關(guān)于Java通俗易懂講解泛型的文章就介紹到這了,更多相關(guān)Java泛型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
借助Maven搭建Hadoop開發(fā)環(huán)境的最詳細(xì)教程分享
在Maven插件的幫助下,VSCode寫Java其實(shí)非常方便,所以本文就來(lái)和大家詳細(xì)講講如何借助maven用VScode搭建Hadoop開發(fā)環(huán)境,需要的可以參考下2023-05-05使用SpringBoot整合Activiti6工作流的操作方法
這篇文章主要介紹了使用SpringBoot整合Activiti6工作流,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07使用spring框架ResponseEntity實(shí)現(xiàn)文件下載
這篇文章主要介紹了使用spring框架ResponseEntity實(shí)現(xiàn)文件下載,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Springboot實(shí)現(xiàn)給前端返回一個(gè)tree結(jié)構(gòu)方法
這篇文章主要介紹了SpringBoot返回給前端一個(gè)tree結(jié)構(gòu)的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Java中為什么要實(shí)現(xiàn)Serializable序列化
在Java編程中,Serializable序列化是一個(gè)常見的概念,它允許對(duì)象在網(wǎng)絡(luò)上傳輸或持久化到磁盤上,本文將深入探討為什么在Java中要實(shí)現(xiàn)Serializable序列化,并通過(guò)示例代碼來(lái)解釋其重要性2023-10-10Java面向?qū)ο髮?shí)現(xiàn)汽車租賃系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java面向?qū)ο髮?shí)現(xiàn)汽車租賃系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Spring Boot2.0整合ES5實(shí)現(xiàn)文章內(nèi)容搜索實(shí)戰(zhàn)
這篇文章主要介紹了Spring Boot2.0整合ES5實(shí)現(xiàn)文章內(nèi)容搜索實(shí)戰(zhàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01詳解SpringBoot中JdbcTemplate的事務(wù)控制
JdbcTemplate是spring-jdbc提供的數(shù)據(jù)庫(kù)核心操作類,那對(duì)JdbcTemplate進(jìn)行事務(wù)控制呢,本文就詳細(xì)的介紹一下2021-09-09