引言
【RE:布丁JAVA】是一個(gè)java小白布丁薩瑪渾渾噩噩工作三年之后發(fā)現(xiàn)自己java基礎(chǔ)不行而重新學(xué)習(xí)java的系列谍倦,喜歡的同學(xué)可以點(diǎn)個(gè)<font color=red >關(guān)注</font>,如果有什么問(wèn)題可以在評(píng)論區(qū)發(fā)表<font color=red >評(píng)論</font>泪勒,謝謝??
愿:<font color=red >天下程序猿頭發(fā)烏黑亮麗</font>
概述
在我剛開(kāi)始學(xué)習(xí)JAVA的時(shí)候經(jīng)持缰看到<T> <K> <T,K> <? extends Number> 等等類(lèi)似的寫(xiě)法,當(dāng)時(shí)就很疑惑圆存,這些到底是什么為什么有的時(shí)候是'T'有的時(shí)候是'K'而有的時(shí)候是'?'叼旋,是有什么規(guī)則么?這些代表的又是什么呢沦辙?我想要使用<J>可以嗎夫植?
下面就要我們帶著這種種疑問(wèn)來(lái)看下我們今天的主題==泛型'==。
1、為什么要使用泛型
我們學(xué)習(xí)泛型详民,首先要明白==我們?yōu)槭裁匆褂梅盒?=
舉個(gè)有點(diǎn)不優(yōu)雅例子
小A過(guò)生日舉行了一個(gè)生意派對(duì)延欠,于是他準(zhǔn)備了一個(gè)箱子。告訴大家沒(méi)人可以把放進(jìn)去一個(gè)食物沈跨。到時(shí)候自己會(huì)把他們一個(gè)一個(gè)的吃掉由捎。于是小B放了蘋(píng)果、小C放了橘子饿凛。
但是狞玛,我放了一坨屎進(jìn)去。
然后小A在生日派對(duì)上就吃了shit發(fā)生了不可以思議的事情涧窒。
于是小A之后再辦這種事情的時(shí)候心肪,會(huì)讓自己的管家檢查放進(jìn)入的是不是食物。不是食物就是不讓放進(jìn)去纠吴。這樣他就不會(huì)吃屎了蒙畴。
而這個(gè)管家就可以認(rèn)為是泛型。為了約束放入箱子?xùn)|西的類(lèi)型呜象。
好了好了,看完上面這個(gè)例子同學(xué)們一定都知道了什么是泛型了碑隆,為什么使用泛型(為了防止吃shit )下面我們就開(kāi)認(rèn)真講一下恭陡。
2、什么是泛型
同學(xué)們都知道在我們開(kāi)發(fā)的時(shí)候錯(cuò)誤可以分為兩種上煤,
編譯時(shí)錯(cuò)誤
在編譯階段由java編譯器發(fā)現(xiàn)的錯(cuò)誤休玩。
如上圖所示,編譯器發(fā)現(xiàn)我們的代碼錯(cuò)誤劫狠,會(huì)提示我們拴疤,而我們開(kāi)發(fā)人員必須要修改錯(cuò)誤之后,才可以通過(guò)編譯独泞。這就是編譯時(shí)錯(cuò)誤
運(yùn)行時(shí)錯(cuò)誤
編譯時(shí)未報(bào)錯(cuò)呐矾,在運(yùn)行時(shí)拋出異常。
如上圖所示在編譯的時(shí)候懦砂,是不會(huì)曝出錯(cuò)誤蜒犯。但是運(yùn)行的時(shí)候就會(huì)拋出==ClassCastException==的錯(cuò)誤,這樣就稱之為運(yùn)行時(shí)錯(cuò)誤荞膘。
從JDK5開(kāi)始所有的Java集合都采用了泛型的機(jī)制罚随。在聲明集合變量的時(shí)候,可以使用‘<>’指定集合中的元素類(lèi)型羽资。
例子如下圖
所以說(shuō):
==泛型就是為了把 ClassCastException 運(yùn)行時(shí)錯(cuò)誤轉(zhuǎn)換成編譯時(shí)錯(cuò)淘菩,是為了約束參數(shù)類(lèi)型而誕生的。==
泛型的主要用于==泛型類(lèi)==屠升、==泛型接口==潮改、==泛型方法==狭郑、==泛型數(shù)組==
下面就讓我們一次來(lái)介紹以下。
3进陡、泛型類(lèi)&泛型接口
首先我們先來(lái)看一個(gè)正常的類(lèi)愿阐,然后我們嘗試把他改造成一個(gè)泛型類(lèi)。
public class Printer {
/**
* 打印文字
*/
Object printText;
public Printer(Object printText) {
this.printText = printText;
}
public Object getPrintText() {
return printText;
}
public void setPrintText(Object printText) {
this.printText = printText;
}
public static void main(String[] args) {
Printer printer = new Printer("測(cè)試打印文本");
// 運(yùn)行時(shí)錯(cuò)誤 拋出ClassCastException
Integer o = (Integer) printer.getPrintText();
}
}
在main方法中代碼調(diào)用printer類(lèi)趾疚,獲取printText進(jìn)行了強(qiáng)制轉(zhuǎn)換編譯可以通過(guò)但是運(yùn)行就會(huì)報(bào)錯(cuò)缨历,拋出ClassCastException錯(cuò)誤。
為了防止這種情況出現(xiàn)糙麦,我們就可以對(duì)printer類(lèi)進(jìn)行改造辛孵。
public class Printer<T> {
/**
* 打印文字
*/
T printText;
public Printer(T printText) {
this.printText = printText;
}
public T getPrintText() {
return printText;
}
public void setPrintText(T printText) {
this.printText = printText;
}
public static void main(String[] args) {
Printer<String> printer=new Printer<String>("這是測(cè)試打印文本");
Integer o = (Integer) printer.getPrintText();// 編譯錯(cuò)誤
String a = printer.getPrintText();// 合法
}
}
就像在定義方法的時(shí)候可以聲明一些方法參數(shù),在定義類(lèi)的時(shí)候我們可以通過(guò)< T >的形式類(lèi)聲明類(lèi)型參數(shù)赡磅,在類(lèi)主題中可以直接引用 T魄缚。
這種帶有類(lèi)型參數(shù)的類(lèi)就被稱為泛型類(lèi)。
上面的printer類(lèi)就是一個(gè)泛型類(lèi)焚廊,他有一個(gè)類(lèi)型參數(shù)T冶匹,而在main方法中初始化printer類(lèi)的時(shí)候指定的T為String,所以在之后的get方法中編譯器會(huì)知道返回值為String咆瘟,所以使用Integer接收的時(shí)候會(huì)編譯錯(cuò)誤嚼隘。而使用String接收的時(shí)候就是合法的。
泛型類(lèi)格式:
修飾詞 class 類(lèi)名 <T> {...}
如:
public class printer <T> {
T content;
}
public class people <J> {
J name;
}
這里的T只是習(xí)慣叫法而已袒餐。如果你樂(lè)意也可以用其他字母代替 比如 B飞蛹、Y、H灸眼。都是可以的卧檐。
一個(gè)泛型類(lèi)也可以有多個(gè)類(lèi)型參數(shù),多個(gè)類(lèi)型參數(shù)放在一個(gè)<>中使用‘,’隔開(kāi)焰宣,例子如下:
public class Printer<T,K> {
Map<T,K> map;
public Map<T, K> getMap() {
return map;
}
public void setMap(Map<T, K> map) {
this.map = map;
}
public static void main(String[] args) {
Printer<String,Integer> printer=new Printer<String, Integer>();
printer.getMap().put("key",1);// 合法
printer.getMap().put(1,"key");// 編譯錯(cuò)誤
}
}
泛型接口與泛型類(lèi)的用法基本一致
泛型接口的格式
修飾詞 interface 接口名稱 <T> {...}
這里我們就舉一個(gè)例子看下:
public interface people <T> {
T setName(T name);
}
4霉囚、泛型方法
在一個(gè)方法中,如果方法的參數(shù)或者返回值中帶有<font color=red>< T ></font>形式的類(lèi)型參數(shù)宛徊,那么這個(gè)方法稱為泛型方法佛嬉。在普通的類(lèi)或者泛型類(lèi)中都可以創(chuàng)建泛型方法。
泛型方法結(jié)構(gòu)
// 返回值 闸天、參數(shù) 暖呕、 方法體 都可以引用 K類(lèi)型參數(shù)
修飾詞 <K> 返回值 方法名稱(參數(shù)) {方法體}
例子如下:
public class Printer<T> {
T printText;
// 不是泛型方 T只是引用了類(lèi)的類(lèi)型變量而已。并沒(méi)有自己定義
public T getPrintText() {
return printText;
}
// 是泛型方法 自己定義了類(lèi)型變量K
public <K> K print(K text) {
System.out.println("打印文本:" + text);
return text;
}
public static void main(String[] args) {
Printer printer=new Printer();
printer.print("測(cè)試打印文本");
}
}
例子中的getPrintText()方法雖然有T但是并不是泛型方 T只是引用了類(lèi)的類(lèi)型變量而已苞氮。并沒(méi)有自己定義湾揽。
而print()方法是泛型方法因?yàn)樽约憾x了類(lèi)型變量K。
5、泛型數(shù)組
泛型數(shù)組是指數(shù)組的類(lèi)型是‘T’的數(shù)組库物,如‘T[]’霸旗;
例子
public class Printer<T> {
// 泛型數(shù)組
public T[] content;
public T[] getContent() {
return content;
}
public void setContent(T[] content) {
this.content = content;
}
public static void main(String[] args) {
Printer<String> printer = new Printer<String>();
printer.setContent(new String[]{"元素1", "元素2"});
System.out.println(printer.getContent()[0]);
}
}
==注意==:在泛型類(lèi)中不能用泛型數(shù)組來(lái)創(chuàng)建數(shù)組實(shí)例。
public class Printer<T> {
// 泛型數(shù)組
public T[] content = new T[10]; //編譯出錯(cuò)戚揭,不能用泛型數(shù)組來(lái)創(chuàng)建數(shù)組實(shí)例诱告。
}
5、extends關(guān)鍵詞
在定義泛型的時(shí)候民晒,我們可以使用extends關(guān)鍵字來(lái)限定類(lèi)型參數(shù)精居,語(yǔ)法格式為
<T extends 類(lèi)名>/<T extends 接口名>
比如我們剛開(kāi)始的例子中,我們想要放入箱子的食物都是甜品潜必,那么我們就可以寫(xiě) <T extends 甜品>
例子:
public class Printer<T extends Number> {
public T content;
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public static void main(String[] args) {
Printer<Integer> printer = new Printer<Integer>(); // 合法靴姿;Integer是Number子類(lèi)
Printer<Double> printer1 = new Printer<Double>(); // 合法; Double是Number子類(lèi)
Printer<String> printer2 = new Printer<String>(); // 編譯錯(cuò)誤磁滚;String不是Number子類(lèi)
}
}
在上面的例子中佛吓,我們?cè)趐rint類(lèi)中定義了一個(gè) <T extends Number>
,代表引入的參數(shù)類(lèi)型必須是Number
的子類(lèi)。
根據(jù)下圖我們知道 Integer 和 Double都是Number的子類(lèi)垂攘,所以合法维雇,而String并不是Number的子類(lèi),所以會(huì)出現(xiàn)編譯錯(cuò)誤晒他。
6谆沃、"?" 通配符
‘?’
是表示一個(gè)不確定的類(lèi)型。由于不是像‘T’
一樣是一個(gè)確定的類(lèi)型仪芒,所以‘?’
無(wú)法用于定義變量或者類(lèi)脖母。一般用于集合中尔邓。
‘?’
可以使用有上限和下限對(duì)類(lèi)型進(jìn)行約束
‘?’
的上限‘<? extends 類(lèi)名/接口>’
List<? extends Number> list = new ArrayList<>();
‘?’
的下限‘<? super 類(lèi)名/接口>’
List<? super Number> list = new ArrayList<>();
關(guān)于通配符還有很多東西可以講,比如List<? extends Number> 無(wú)法add除了null只為的元素捶牢,這個(gè)我之后準(zhǔn)備專門(mén)開(kāi)一篇講解哟沫,如果有想要知道的同學(xué)可以點(diǎn)個(gè)關(guān)注饺蔑。
7、注意事項(xiàng)
1嗜诀、泛型只在編譯期間有效猾警,在編譯之后的字節(jié)碼文件中就會(huì)被清除。
所以以下兩個(gè)list對(duì)象是相等的
List<Srting> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass()==list2.getClass()) // 打印true
因?yàn)榉盒驮诰幾g之后隆敢,所以編譯器也是不允許在一個(gè)類(lèi)定義兩個(gè)同名的方法发皿,參數(shù)分別是List<T>
和List<K>
public class Printer<T,K> {
public void print(List<T> list){
}
// 編譯錯(cuò)誤,非法的方法重載
public void print(List<K> list2){
}
}
2拂蝎、不可以對(duì)泛型進(jìn)行強(qiáng)制轉(zhuǎn)換穴墅,這樣存在安全隱患,會(huì)導(dǎo)致拋出ClassCastException異常。
Collection list1 = new ArrayList<Integer>();
list1.add(1);
List<String> list2 = new ArrayList<String>();
list2 = (ArrayList<String>) list1;
for (String s:list2){
System.out.println(s); // 拋出異常ClassCastException
}
3玄货、不能對(duì)泛型進(jìn)行instanceof操作皇钞。
public void print(List<K> list){
// 編譯錯(cuò)誤
if(list instanceof Collection<String>){
}
}
8、參考
- 對(duì)Java通配符的個(gè)人理解(以集合為例)
- JAVA面向?qū)ο缶幊?第2版)
- java泛型 通配符詳解及實(shí)踐
8松捉、結(jié)語(yǔ)
本片文章主要講了泛型的使用夹界,我們知道了泛型主要是為了是編譯器在編譯的時(shí)候能夠判斷我們的參數(shù)是否正確,從而避免運(yùn)行時(shí)錯(cuò)誤隘世。并且可以簡(jiǎn)化代碼編寫(xiě)可柿,無(wú)需進(jìn)行多余的強(qiáng)制轉(zhuǎn)換。
然后我們學(xué)習(xí)了如何定義 泛型類(lèi)以舒、泛型方法趾痘、泛型接口 等。
如果文章有什么錯(cuò)誤后者同學(xué)們有疑問(wèn)的地方蔓钟,歡迎評(píng)論留言永票,
如果您喜歡,歡迎<font color=red>關(guān)注滥沫、點(diǎn)贊</font> 謝謝??