引言
【RE:布丁JAVA】是一個java小白布丁薩瑪渾渾噩噩工作三年之後發現自己java基礎不行而重新學習java的系列,喜歡的同學可以點個關注,如果有什麼問題可以在評論區發表評論,謝謝🙏
願:天下程序猿頭髮烏黑亮麗
概述
在我剛開始學習JAVA的時候經常看到 <T,K> <? extends Number> 等等類似的寫法,當時就很疑惑,這些到底是什麼爲什麼有的時候是’T’有的時候是’K’而有的時候是’?’,是有什麼規則麼?這些代表的又是什麼呢?我想要使用可以嗎?
下面就要我們帶着這種種疑問來看下我們今天的主題泛型’。
1、爲什麼要使用泛型
我們學習泛型,首先要明白我們爲什麼要使用泛型
舉個有點不優雅例子
小A過生日舉行了一個生意派對,於是他準備了一個箱子。告訴大家沒人可以把放進去一個食物。到時候自己會把他們一個一個的喫掉。於是小B放了蘋果、小C放了橘子。
但是,我放了一坨屎進去。
然後小A在生日派對上就吃了shit發生了不可以思議的事情。
於是小A之後再辦這種事情的時候,會讓自己的管家檢查放進入的是不是食物。不是食物就是不讓放進去。這樣他就不會喫屎了。
而這個管家就可以認爲是泛型。爲了約束放入箱子東西的類型。
好了好了,看完上面這個例子同學們一定都知道了什麼是泛型了,爲什麼使用泛型(爲了防止喫shit )下面我們就開認真講一下。
2、什麼是泛型
同學們都知道在我們開發的時候錯誤可以分爲兩種,
編譯時錯誤
在編譯階段由java編譯器發現的錯誤。
如上圖所示,編譯器發現我們的代碼錯誤,會提示我們,而我們開發人員必須要修改錯誤之後,纔可以通過編譯。這就是編譯時錯誤
運行時錯誤
編譯時未報錯,在運行時拋出異常。
如上圖所示在編譯的時候,是不會曝出錯誤。但是運行的時候就會拋出ClassCastException的錯誤,這樣就稱之爲運行時錯誤。
從JDK5開始所有的Java集合都採用了泛型的機制。在聲明集合變量的時候,可以使用‘<>’指定集合中的元素類型。
例子如下圖
所以說:
泛型就是爲了把 ClassCastException 運行時錯誤轉換成編譯時錯,是爲了約束參數類型而誕生的。
泛型的主要用於泛型類、泛型接口、泛型方法、泛型數組
下面就讓我們一次來介紹以下。
3、泛型類&泛型接口
首先我們先來看一個正常的類,然後我們嘗試把他改造成一個泛型類。
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("測試打印文本");
// 運行時錯誤 拋出ClassCastException
Integer o = (Integer) printer.getPrintText();
}
}
在main方法中代碼調用printer類,獲取printText進行了強制轉換編譯可以通過但是運行就會報錯,拋出ClassCastException錯誤。
爲了防止這種情況出現,我們就可以對printer類進行改造。
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>("這是測試打印文本");
Integer o = (Integer) printer.getPrintText();// 編譯錯誤
String a = printer.getPrintText();// 合法
}
}
就像在定義方法的時候可以聲明一些方法參數,在定義類的時候我們可以通過< T >的形式類聲明類型參數,在類主題中可以直接引用 T。
這種帶有類型參數的類就被稱爲泛型類。
上面的printer類就是一個泛型類,他有一個類型參數T,而在main方法中初始化printer類的時候指定的T爲String,所以在之後的get方法中編譯器會知道返回值爲String,所以使用Integer接收的時候會編譯錯誤。而使用String接收的時候就是合法的。
泛型類格式:
修飾詞 class 類名 <T> {...}
如:
public class printer <T> {
T content;
}
public class people <J> {
J name;
}
這裏的T只是習慣叫法而已。如果你樂意也可以用其他字母代替 比如 B、Y、H。都是可以的。
一個泛型類也可以有多個類型參數,多個類型參數放在一個<>中使用‘,’隔開,例子如下:
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");// 編譯錯誤
}
}
泛型接口與泛型類的用法基本一致
泛型接口的格式
修飾詞 interface 接口名稱 <T> {...}
這裏我們就舉一個例子看下:
public interface people <T> {
T setName(T name);
}
4、泛型方法
在一個方法中,如果方法的參數或者返回值中帶有< T >形式的類型參數,那麼這個方法稱爲泛型方法。在普通的類或者泛型類中都可以創建泛型方法。
泛型方法結構
// 返回值 、參數 、 方法體 都可以引用 K類型參數
修飾詞 <K> 返回值 方法名稱(參數) {方法體}
例子如下:
public class Printer<T> {
T printText;
// 不是泛型方 T只是引用了類的類型變量而已。並沒有自己定義
public T getPrintText() {
return printText;
}
// 是泛型方法 自己定義了類型變量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("測試打印文本");
}
}
例子中的getPrintText()方法雖然有T但是並不是泛型方 T只是引用了類的類型變量而已。並沒有自己定義。
而print()方法是泛型方法因爲自己定義了類型變量K。
5、泛型數組
泛型數組是指數組的類型是‘T’的數組,如‘T[]’;
例子
public class Printer<T> {
// 泛型數組
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]);
}
}
注意:在泛型類中不能用泛型數組來創建數組實例。
public class Printer<T> {
// 泛型數組
public T[] content = new T[10]; //編譯出錯,不能用泛型數組來創建數組實例。
}
5、extends關鍵詞
在定義泛型的時候,我們可以使用extends關鍵字來限定類型參數,語法格式爲
<T extends 類名>/<T extends 接口名>
比如我們剛開始的例子中,我們想要放入箱子的食物都是甜品,那麼我們就可以寫 <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子類
Printer<Double> printer1 = new Printer<Double>(); // 合法; Double是Number子類
Printer<String> printer2 = new Printer<String>(); // 編譯錯誤;String不是Number子類
}
}
在上面的例子中,我們在print類中定義了一個 <T extends Number>
,代表引入的參數類型必須是Number
的子類。
根據下圖我們知道 Integer 和 Double都是Number的子類,所以合法,而String並不是Number的子類,所以會出現編譯錯誤。
6、"?" 通配符
‘?’
是表示一個不確定的類型。由於不是像‘T’
一樣是一個確定的類型,所以‘?’
無法用於定義變量或者類。一般用於集合中。
‘?’
可以使用有上限和下限對類型進行約束
‘?’
的上限‘<? extends 類名/接口>’
List<? extends Number> list = new ArrayList<>();
‘?’
的下限‘<? super 類名/接口>’
List<? super Number> list = new ArrayList<>();
關於通配符還有很多東西可以講,比如List<? extends Number> 無法add除了null只爲的元素,這個我之後準備專門開一篇講解,如果有想要知道的同學可以點個關注。
7、注意事項
1、泛型只在編譯期間有效,在編譯之後的字節碼文件中就會被清除。
所以以下兩個list對象是相等的
List<Srting> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass()==list2.getClass()) // 打印true
因爲泛型在編譯之後,所以編譯器也是不允許在一個類定義兩個同名的方法,參數分別是List<T>
和List<K>
public class Printer<T,K> {
public void print(List<T> list){
}
// 編譯錯誤,非法的方法重載
public void print(List<K> list2){
}
}
2、不可以對泛型進行強制轉換,這樣存在安全隱患,會導致拋出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、不能對泛型進行instanceof操作。
public void print(List<K> list){
// 編譯錯誤
if(list instanceof Collection<String>){
}
}
8、參考
- 對Java通配符的個人理解(以集合爲例)
- JAVA面向對象編程(第2版)
- java泛型 通配符詳解及實踐
8、結語
本片文章主要講了泛型的使用,我們知道了泛型主要是爲了是編譯器在編譯的時候能夠判斷我們的參數是否正確,從而避免運行時錯誤。並且可以簡化代碼編寫,無需進行多餘的強制轉換。
然後我們學習瞭如何定義 泛型類、泛型方法、泛型接口 等。
如果文章有什麼錯誤後者同學們有疑問的地方,歡迎評論留言,
如果您喜歡,歡迎關注、點贊 謝謝🙏