前幾天總結了下學習一個知識點的步驟,之前寫博客都是想到什麼,就寫什麼,沒有一定的條理,接下來就看看如何學習
一個新的知識點,也不知道,這種步驟是否OK,反正以後學習新知識就這樣來吧!
學習一個知識點的四部曲!
1.什麼是它(泛型)?
2.用它(泛型)有什麼好處? (這裏得拋磚引玉,先講沒有它會出現什麼問題,有了它解決了什麼問題)
突然想到了,先抑後揚,也叫欲揚先抑這種修辭手法
3.它主要包含哪些知識點(列出大綱)?
4.怎麼使用它?
一:什麼是它(泛型)?
Java泛型是jdk1.5中引入的一個新特性,其本質是參數化類型,
也就是說所操作的數據類型被指定爲一個參數(type parameter)
這種參數類型可以用在類、接口和方法的創建中,分別稱爲泛型類、泛型接口、泛型方法 (我百度百科過來的)
二:用它(泛型)有什麼好處?
2.1沒它時
public class Demo1 {
public static void main(String[] args) {
test1();
test2();
}
private static void test2() {
/**
* 不使用泛型的話,把元素添加到集合時,就失去的元素的狀態信息,集合就只知道它是一個Object
* 對象,因此到時候還得強制類型轉換,也可能會引發異常
*/
List list = new ArrayList();
//Object = "hello";
list.add("hello");
list.add("world");
list.add("java");
}
private static void test1() {
/**
* 不使用泛型,集合對元素類型都沒有限制,假如我只想創建一個裝Dog的集合
* 但這時程序,也可以把Cat類型的元素給我添加進去!所以到時候可能會引發異常
*/
List list = new ArrayList();
list.add(11);
list.add(11.0);
list.add(11.0F);
list.add(true);
list.add("hello World");
for (Object obj : list) {
Integer i = (Integer)obj;
System.out.println(i);
}
}
}
通過上面代碼,我們得知,沒有使用泛型的時候,大概會出現這麼幾點問題
a. 失去了元素的狀態信息,到時候要強制轉換,可能引發ClassCastException,
b. 使集合對元素的類型沒有限制,還出現了黃色警告線
2.2有它時
public class Demo2 {
public static void main(String[] args) {
/**
* 針對上述問題:
* (1) 不能限制集合元素的類型
* (2) 添加到集合,就失去了原本的類型,一律作爲Object處理
* 導致後面還得強制類型轉換!
* ..........
*/
/**
* (1)有了泛型之後,我讓該集合只能裝Dog就只能裝Dog,不能裝Cat
* 裝Cat的話在編譯時就不會通過
*
* (2)有了泛型之後,元素添加到集合時,不會丟失原本的類型,
* 就不需要強制轉換了
*/
List<Integer> list = new ArrayList<Integer>();
list.add(11);
list.add(22);
list.add(33);
// list.add("44"); //編譯報錯,限制了元素的類型
for(Integer i : list) {
System.out.println(i); //不需要強制
}
}
}
有了泛型之後,我們發現上面的問題,都得以解決!!
三:它主要包含哪些知識點(列出大綱)?
3.1泛型類
3.2泛型接口
3.3泛型方法
3.4通配符 ?
3.5設定通配符的上限 <? extends T>
3.6設定通配符的下限<? super T>
3.7泛型的擦除機制
3.1泛型類
泛型類其實還是挺簡單的,就是在類後面加一個<>,裏面放一個字母, 放什麼字母是沒有限制的,但一般是放如下字母(因爲有含義)
T Type表示類型
K V 表示鍵值對中的key value
E 代表Element
N 代表Number
?表示不確定的類型
例子:
public class Demo4 {
public static void main(String[] args) {
Apple<String> apple = new Apple<String>("紅富士");
String info = apple.getInfo();
System.out.println(info);
}
}
/**
* 定義了一個帶泛型聲明的Apple類,其類型形參是T
* T,就相當於普通方法中的形參,就相當於一個佔位符,
* 到時候你給什麼類型,它就變成了什麼類型,跟個模板似的!
*
* tips: 覺得剛開始寫泛型類不習慣,可以先用String類型代替,再
* 用T代替會比較好
*
* @author wzj
*/
class Apple<T>{
private T info;
public Apple(T info) {
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return this.info;
}
}
3.2:泛型接口: 跟泛型類類似,略
3.3:泛型方法
定義:就是在方法聲明時定義一個或者多個類型形參:修飾符 <T,S> 返回值類型 方法名(參數列表)
泛型方法跟普通方法比起來,就多了個類型形參,是放在方法修飾符和返回值類型之間
格式: 修飾符 <T, S> 返回值類型 方法名(形參列表){
方法體....
}
例子:
public class Demo5{
public static void main(String[] args) {
/**
* 需求: 定義一個方法,將數組中的元素添加到 集合中去
* 要求是我這個數組裏面的元素是什麼類型,對應集合泛型的類型也是如此
*
* java數組具有協變性,而java集合不是協變的;
*/
String[] arr = {"1","2","3"};
//Collection<String> c = new ArrayList<String>();//java集合不是協變的;
Collection<Object> c = new ArrayList<Object>();
fromArrToCollection(arr,c);
}
private static void fromArrToCollection(Object[] arr, Collection<Object> c) {
for (Object ele : arr) {
c.add(ele);
}
}
}
上面由於集合不是協變的,所以ArrayList<String>不能作爲實參傳過來,雖然上面如果就用Collection<Object>來接收
任意類型數組,也是可以的,但是到時候取出來的時候,還得強轉,既然使用了泛型,就別要去強轉
所以下面我們使用泛型方法,來改進一下上面的代碼
public class Demo6{
public static void main(String[] args) {
String[] arr = {"1","2","3"};
Collection<String> c = new ArrayList<String>();
fromArrToCollection(arr,c);
//遍歷集合c
for (String s : c) {
//無需強轉
System.out.println(s);
}
}
private static <T> void fromArrToCollection(T[] arr, Collection<T> c) {
for (T ele : arr) {
c.add(ele);
}
}
}
上面可以實現你數組中的元素是什麼類型,集合到時候存的就是什麼類型的元素,再也無需強轉!
3.4通配符 ?
爲了表示所有泛型List的父類, List<?> 就出現了
需求: 定義一個可以打印所有泛型List的方法
public class Test {
public static void main(String[] args) {
//定義一個可以打印所有泛型List的方法
List<String> strList = new ArrayList<String>(Arrays.asList("11","22","33"));
// printList(strList);//編譯報錯,List<Object>不是所有List泛型的父類,List<?>纔是
printList2(strList);
}
private static void printList(List<Object> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList2(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList3(List list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
可能讀者看到這裏會想, 感覺List 和 List<?> 差不多,就很容易把概念混淆
所以這裏稍微介紹一下他們之間的區別!
List,List<Object>,List<?> 之間的區別
1.List 和 List<Object>
明顯List作爲方法形參的時候,可以接受任何泛型List,而List<Object>不行
2.List 和 List<?>
既然上面說到List也可以接收任何泛型List,是不是和List<?>一致呢?
其實還是有點區別的,List<?>被賦值完,之後就不能隨意的往裏面添加東西了
而List是可以的
接下來就演示一下,第二點說明
public class Demo6 {
public static void main(String[] args) {
//演示下List 和 List<?> 的區別!
List<String> list = new ArrayList<String>(Arrays.asList("hello","world"));
printList(list);
printList2(list);
}
private static void printList2(List<?> list) {
// list.add("1"); //這裏會出現編譯錯誤
for (Object obj : list) {
System.out.println(obj);
}
}
private static void printList(List list) {
list.add("java");
for (Object obj : list) {
System.out.println(obj);
}
}
}
3.5設定通配符的上限 <? extends T> & 設定通配符的下限<? super T>
對泛型的類型參數,限制了一定的範圍,
可以通過該圖來記憶,因爲繼承其實就是通過樹來描述的
T在上面:就表示通配符的上限,範圍是<= T
T在下面:就表示通配符的下限,範圍是>= T
泛型只是在 編譯期 保證對象類型相同的技術。真正在代碼的運行期,jvm會擦出泛型的存在。
所以我們可以利用反射技術爲一個已指定泛型的集合添加一個不符合泛型要求的元素,因爲反射的生效期在運行期,泛型無法進行攔截。
因此,泛型指定的元素不具有繼承的特性。不能將泛型中的派生類類型複製給基類類型。
從而出現了通配符的技術,爲了解決在泛型中不能像正常JAVA類中的繼承關係
下面的代碼,演示了一下通配符上限和下限的基本用法
public class Demo7 {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<Integer>(Arrays.asList(11));
add(intList);
List<Number> numList = new ArrayList<Number>(Arrays.asList(11));
add2(numList);
}
public static void add(List<? extends Number> list) {
//通配符的上限: 不能put(除了null,因爲null是所有類型的實例),只能get
// list.add(1); //編譯報錯
Number number = list.get(0);//因爲設定了上限,而上限是Number,所以你裏面的元素,絕對是屬於Number的
System.out.println(number);
}
public static void add2(List<? super Number> list) {
//通配符的下限: 不能get(除了可以獲取Object的引用),只能put
list.add(22);
// Number num = list.get(0);//編譯報錯
Object obj = list.get(0);
}
}
然後看一下通配符的上限和下限什麼時候用,有個PECS原則!
PECS(Producer Extends Consumer Super)
1. 頻繁往外讀取內容的,適用用上界(Extends)
2.經常往裏插入內容的,適用用下界(Super)
例子: Collections.copy(List<? super T> dest, List<? extends T> src);
public class Demo9 {
public static void main(String[] args) {
/**
* Collections.copy(List<? super T> dest, List<? extends T> src)
* 設想爲何jdk要如此設計此方法
*
* 這不是將src集合中的元素拷貝到dest集合中,他是拷貝對象
*
*/
System.out.println("============測試1===========");
List<String> src = new ArrayList<String>(Arrays.asList("1","2","3"));
List<String> dest = new ArrayList<String>(Arrays.asList("11","22","33"));
Collections.copy(dest, src);
System.out.println(dest);
//單純上面這樣看起來,感覺沒必要用上面通配符,用個泛型方法就行了
Mycopy(dest,src);
System.out.println(dest);
System.out.println("============測試2===========");
List<Number> dest2 = new ArrayList<Number>(Arrays.asList(11,22,33));
List<Integer> src2 = new ArrayList<Integer>(Arrays.asList(1,2,3));
Collections.copy(dest2, src2);
System.out.println(dest2);
/**
* 這時我們設計的方法,就開始頂不住了,因爲第一個dest2設置泛型類型參數是Integer
* 第二個src2設置泛型類型參數是Number,編譯器傻傻分不清楚,所以就報錯了
*而Collections.copy(); 爲什麼可以呢 ?,因爲他們可以解決這種泛型之間的繼承關係!
*/
// Mycopy(dest2,src2); //編譯報錯
}
private static <T> void Mycopy(List<T> dest, List<T> src) {
int srcSize = src.size();
int destSize = dest.size();
if(srcSize > destSize)
throw new RuntimeException("src|dest長度不符合規則");
for(int x=0; x<srcSize; x++) {
T ele = src.get(x);
dest.set(x,ele);
}
}
}
3.7泛型的擦除機制
簡單對擦除機制做一個說明,泛型是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。
比如: List<String> 和 List<Integer> 在最終運行期的時候,都會變成List
public class Demo8 {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass() == intList.getClass());//true
}
}
接下來是在牛客網看到一個總結
/**
* https://www.nowcoder.com/questionTerminal/9bc2d446173147b3b28b31568a6c4706
* 記憶方法:
* 1. 只看尖括號裏面的 !!,明確點和範圍兩個概念就行
* 2.如果尖括號中是一個類,那就把他看成一個點: 比如List<Number>,List<Object>...
* 3.如果尖括號中帶有?號, 那就把他看成一個範圍,
* a.<? extends Number> <=Number的範圍,這就是爲什麼叫類型通配符的上限
* b.<? super Number> >=Number的範圍, 這就是爲什麼叫類型通配符的下限
* c.<?>: 表示全部範圍
* 4.<>中點,相互賦值是錯的,除了兩個相同的點:List<String> list = new ArrayList<String>();
* 5.<>中小範圍賦值給大範圍是對的,否則是錯的
* 例子:
* Y: List<? extends Number> list = new ArrayList<? extends String>();
* N: List<? extends String> list = new ArrayList<? extends Number>();
*
* 6.如果某個點,包含在範圍中,那麼可以賦值,否則不能賦值!
*
* Y: List<? extends Number> list = new ArrayList<Integer>();
* N: List<? extends Number> list = new ArrayList<String>(); //String根本就不是Number的子類!
*
* 注意: 上面的規則這是快速讓你記住,然後使用,並不是泛型真正的核心!而且有些特殊的例子不適用!
* 例子:List<?> list = new ArrayList<? extends Number>();
* 這裏按照上面的說法,按道理是對的,但結果是 不通過的
*
* @author wzj
*
*/
class A{}
class B extends A{}
class C extends A{}
class D extends B{}
public class Demo5 {
public static void main(String[] args) {
/**
* Lsit lsit = ArrayList<A>();
* 正確嗎? Y: 範圍包含點,所以是正確的
*
*
* List<A> list = new ArrayList<B>();
* 正確嗎? N:雖然B是A的子類,但ArrayList<B> 卻不是 ArrayList<A>的子類
* 只有兩個點,相同的情況下纔可以賦值
*
* List<?> list = ArrayList<Object>();
* 正確嗎? Y:這裏不要管是不是object,只要尖括號裏是一個類,就把他想成一個點
* 那這裏肯定,範圍包括點啊
*
*
* List<? extends B> list = new ArrayList<D>();
* 正確嗎? Y: 範圍所以包含點,但也要看,點是否在範圍內
* 範圍: <=B , D是B的子類,所以該點是包含在範圍內的
*
*
* List<A> list = new ArrayList<? extends A>();
* 正確嗎? N: 點不能包含範圍
*
*
* List<Integer> list = new ArrayList<Object>();
* 正確嗎? N: 兩個不同的點之間,不能相互賦值
*
* List<? extends A> list = new ArrayList<? extends B>();
* 正確嗎? Y: 由於B是A的子類, 所以? extends A的範圍可以包含於 ? extends B的
*
*
*/
/**
* 1.List 相等於 List<Object> 嗎?
* N: List list = new ArrayList<Integer>();
* 如果List 是 List<Object>的話, 那麼上面的代碼按道理是編譯報錯的,但是事實上是可以的
* 2.List 相等於 List<?> 嗎?
* N: List<?> list = new ArrayList<? extends Number>();
*/
}
}
其實上面的總結,我感覺只能幫助我們快速的記憶,如何使用不會報錯,但沒有涉及到泛型真正的核心,但整體來說總結的還是蠻到位的,對於初學者來說還是挺友好的,畢竟泛型屬於高級知識點,不可能一下子把他學會的,需要一個過程,像我現在還是隻能看的懂,但具體不知道如何場景下使用,還不是很熟練,需要通過大量的練習,才能做到遊刃有餘,多看看java集合中是如何使用的,爲什麼要這樣使用!!!
最後有什麼寫的不好的,希望各位可以不吝指出
覺得對你有幫助的,想打賞的可以打賞一下,哈哈哈,多少無所謂,這也是對我一種支持與鼓勵嗎,最後不喜勿噴
有志同道合的小夥伴可以加QQ羣討論:897992110