源码必备知识:泛型
?前言?
📘 博客主頁:to Keep博客主頁
🙆歡迎關注,👍點贊,📝留言評論
?首發時間:2022年3月3日
📨 博主碼云地址:博主碼云地址
📕參考書籍:java核心技術 卷1
📢編程練習:牛客網+力扣網
由于博主目前也是處于一個學習的狀態,如有講的不對的地方,請一定聯系我予以改正!!!
學習目標:
1 基本掌握泛型的基本概念與運用
2 能夠理解并且看懂java底層源碼的內容即可
文章目錄
- 一 泛型的引入
- 二 泛型的語法
- 2.1 小結
- 2.2 擦除機制(javap - c命令)
- 三 泛型的上界
- 3.1 語法格式
- 3.2 較為復雜的上界
- 3.3 泛型中的父子類
- 3.4 泛型方法
- 3.5 定義語法
- 3.6 非靜態的方法
- 四 通配符
- 4.1通配符的上界
- 4.2 通配符確認父子關系
- 4.3 通配符上界的特點
- 4.4 通配符的下界
- 4.5 通配符的下界-父子類的關系
- 4.6 通配符下界的特點
- 五 總結:
一 泛型的引入
根據泛型的一個概念,在這里我們可以先寫一個自己實現的一個簡易的順序表,不需要考慮其他的,只需借助這個代碼來幫助自己理解泛型
class myarraylist{public Object[] objects = new Object[10];//把val值放到pos位置下public void set(int pos,Object val){objects[pos]=val;}//獲取pos位置下的值public Object get(int pos){return objects[pos];} } public class TestDemo {public static void main(String[] args) {myarraylist myarraylist = new myarraylist();myarraylist.set(0,1);//放入整型myarraylist.set(1,"hello");//放入字符串myarraylist.set(2,' ');//放入字符} }我們不難發現,所創建的對象不僅僅可以存放整形,字符串,以及字符,也就是任何數據類型都可以存放,而且當我們獲取元素時,我們也不難發現,此時會發向下轉型,導致編譯不通過而報錯,此時需要強制類型轉換,轉換成(Object)類型
那么有沒有一種語法支持可以存放我們指定的類型,并且不用在強制類型轉換這么麻煩,那么接下來所要學習的泛型了。
二 泛型的語法
class 泛型類名稱<類型形參列表> {
// 這里可以使用類型參數
}
class ClassName<T1, T2, …, Tn> {
}
根據泛型的語法規則,此時我們就可以改寫一下上面的代碼:
class myarraylist<T>{public T[] objects = (T[])new Object[10];//把val值放到pos位置下public void set(int pos,T val){objects[pos]=val;}//獲取pos位置下的值public T get(int pos){return objects[pos];} } public class TestDemo {public static void main(String[] args) {myarraylist<Integer> myarraylist = new myarraylist<>();myarraylist.set(0,1);//放入整型myarraylist.set(1,3);//myarraylist.set(2,"array");int a = myarraylist.get(0);} }2.1 小結
1 是一個占位符,表示當前類是一個泛型類(一般還可能用,等來表示)
2 結合圖片和代碼,我們可以發現當這個對象利用泛型指定要存儲的類型,那么只能存放這一種類型,并且如果放入的不是指定的這種類型,那么編譯器就會自動報錯,這也說明了我們泛型作用所處于的機制是在編譯時期
3 一般在創建對象的時候,第二個<>會根據第一個填入的類型而確定,所以是省略掉的,可以不用填
4 仔細發現,我們此時也不用進行強制類型轉換了
5 在這里也許有人會奇怪,為什么不能new一個T類型的數組,而是new了一個Object數組,之后強制轉換成為T類型數組,這就和我們泛型的擦除機制有關了,這里也提示一下,我們這樣的寫法也是不對的,真正的創建數組是要根據反射來進行創作的,我們只是為了讓代碼不報錯,能夠進行運行!
6 泛型: 就是一種作用在編譯時期的語法,目的就是指定當前的容器,要持有什么類型的對象,讓編譯器去做檢查??梢宰屛覀冹`活的利用創建不同的對象可以放不同的類型。
2.2 擦除機制(javap - c命令)
我們通過javap -c去看一下底下的字節碼文件,我們可以發現,所有的T都被替換成了Object了,所以java泛型當中所有的T在運行時就都會變成Object(而當我們去打印myarraylist這個引用時,會發現后面是不帶尖括號的,這就是擦除機制而擦除的)
擦除機制的解惑
(并不是所有的都會被擦除機制擦除成Object,如何有上界,類型擦除就會變成上界,例如下面即將講的Number和Comparable上界進行類型擦除時,就會擦成Number或者Comparable)
三 泛型的上界
在定義泛型類時,我們需要對傳入的類型變量進行約束
3.1 語法格式
class 泛型類名稱<類型形參 extends 類型邊界> {
}
此時我們可以有如下代碼:
在語法種規定,只接受Number的子類型作為T的類型實參,顯然String不是Number的子類型,所以編譯器才會報錯!
3.2 較為復雜的上界
此時較為復雜的情況就是當上界出現的是一個Comparatable接口時,此時,我們寫了一個函數,是求數組中的最大值,但是這里是不能直接比較的,如果是一個引用類型是不能夠用大于或者小于號來判斷的,正確的寫法如下:
這里要特別注意一下,如果需要Comparable進行比較是需要實現Comparable接口,所以這里的T類型是必須符合實現了Comparable的
3.3 泛型中的父子類
由于擦除機制的存在,所以是不能都會被擦除,所以是沒有父子類關系的。
3.4 泛型方法
3.5 定義語法
方法限定符 <類型形參列表> 返回值類型 方法名稱(形參列表){}
3.6 非靜態的方法
非靜態的泛型方法是可以省略<類型形參列表>。
靜態的泛型方法
靜態的是不能夠省略的,因為靜態的是不依賴于對象的,而是屬于類的,當沒有創建對象,是不會有T傳過來的,所以必須要指明<類型形參列表>
四 通配符
? 用于在泛型的使用,即為通配符,就是解決泛型中父子類的問題
泛型T就像是個變量,等著你將來傳一個具體的類型,而通配符則是一種規定,
規定你能傳哪些參數
4.1通配符的上界
語法格式:
<? extends 上界>我們先假設:
Animal
Cat extends Animal
Dog extends Animal
此時我們可以對比print1與print2,我們可以發現print1是泛型上界,print2使用的是通配符,print1與print2有著共同的特點,就是放入的元素必須是Animal的子類
兩者區別:
1 泛型方法在靜態是必須要寫出類型形參列表,而通配符是不用的
2 對于print1,一個列表中所能傳入的只能是Animal中的其中一種子類,而print2就不一樣了,一個列表中不僅僅可以放Animal一種,可以放多種。
4.2 通配符確認父子關系
MyArrayList<? extends Number> 是 MyArrayList 或者 MyArrayList的父類類型
MyArrayList<?> (MyArrayList<? extends Object > )是 MyArrayList<? extends Number> 的父類型
4.3 通配符上界的特點
只適合讀取數據,不合適寫入數據:
ArrayList arrayList1 = new ArrayList<>();
ArrayList arrayList2 = new ArrayList<>();
List<? extends Number> list = arrayList1;
//list.add(1,1);//報錯,此時list的引用的子類對象有很多,再添加的時候,任何子類型都可以,為了安全,java不讓這樣進行添加操作。
Number a = list.get(0);//可以通過
Integer i = list.get(0);//編譯錯誤,只能確定是Number子類,所以不一定是Integer類型
4.4 通配符的下界
<? super 下界>例如:<? super Integer>//代表 可以傳入的實參的類型是Integer或者Integer的父類類型
4.5 通配符的下界-父子類的關系
MyArrayList<? super Integer> 是 MyArrayList的父類類型
MyArrayList<?> 是 MyArrayList<? super Integer>的父類類型
4.6 通配符下界的特點
前提:假設student類繼承person類,則:
ArrayList<? super Person> list = new ArrayList();
//ArrayList<? super Person> list2 = new ArrayList();//編譯報錯,list2只能引用Person或者Person父類類型的list
list.add(new Person());//添加元素時,只要添加的元素的類型是Person或者Person的子類就可以
list.add(new Student());
Person s = list.get(0);//error
Object s = list.get(0);//可以
在放入數據的時候,只要是person的子類或者person也可以,但是要注意的是,在讀取數據的時候,不能像通配符上界那樣,利用person去接收,正確的做法應該利用Object去接收,這是因為下界是引用其父類,存數據類型的時候只能存入(變成的類型為)當前類或者當前類的子類,從而取出數據的時候并不能確定其實例數據類型。
五 總結:
對于泛型以及通配符,我們以自己要達到學習目標為主即可,不必過多糾結,能看懂源碼就可以。以上僅代表個人理解,如有錯誤,希望多多指教!及時改正!
總結
- 上一篇: 常见的算法排序(2)
- 下一篇: Map与Set