2022-10-18
泛型 類型
泛型由入門到精通
Hi,小伙伴你好~歡迎進入泛型的學習,在學習之前友情提醒一下:學習泛型需要小伙伴們具備一定的javaSE基礎,如果之前小伙伴們沒有接觸過java,大家可以移步到千鋒北京java好程序員的javaSE課程進行學習。
在正式開始學習之前,我們先來看一段經常書寫的代碼,分析一下代碼存在那些問題?
代碼如下:
public class GenericsDemo {
public static void main(String[] args) {
//1.創建一個List對象
List list = new ArrayList();
//2.向List中添加數據
list.add("python");
list.add("java");
list.add(66);
//3.遍歷集合
for (int i = 0; i <list.size() ; i++) {
//4.把集合中的每個元素轉成String類型
String ele = (String) list.get(i);
//5.打印-測試結果
System.out.println("元素的值:"+ele);
}
}
}
運行代碼,會報如下圖的異常:
那么小伙們,我們來分析一下原因,到底是因為什么報這個異常呢?
在代碼中我們定義了一個List類型的集合,先向其中加入了兩個String類型的值,隨后加入一個Integer類型的值。這是完全允許的,因為此時list默認的類型為Object類型,所以在代碼編譯期間沒有任何問題。
但是在運行代碼時,由于list集合中既有String類型的值,又有Integer類型的值,致使list集合無法區分值是什么類型,很容易出現上圖中的錯誤。因為編譯階段正常,而運行時會出現“java.lang.ClassCastException”異常。因此導致此類錯誤編碼過程中不易發現。
分析完了,小伙們現在明白了吧,通過分析我們發現上述代碼主要存在兩個問題:
當我們將數據存入集合時,集合不會記住數據的類型,默認數據類型都是Object。
當我們遍歷集合中的數據時,人為進行強制類型轉換,很容易報“java.lang.ClassCastException”。強制類型轉換異常
那么有沒有什么辦法可以使集合能夠記住集合內元素各類型,且能夠達到只要編譯時不出現問題,運行時就
不會出現“java.lang.ClassCastException”異常呢?
答案就是使用泛型。
那么什么是泛型呢?
第一關 讓我們一起走入泛型
1.泛型的概述
1.1 什么是泛型
泛型,泛指任意類型,可以應用在接口上,類上,變量上,方法上,以及方法的參數中。
百度百科介紹:
泛型是jdk1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。
在jdk1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對于強制類型轉換錯誤的情況 ,編譯器可能不提示錯誤,在運行的時候才出現異常,這是一個安全隱患。
泛型的好處:使用泛型,首先可以通過IDE進行代碼類型初步檢查,然后在編譯階段進行編譯類型檢查,以保證類型轉換的安全性;并且所有的強制轉換都是自動和隱式的,可以提高代碼的重用率。
個人理解:
簡單來說:泛型,即“參數化類型”,那么類型“參數化”到底怎么理解呢?
顧名思義,類型“參數化”就是將類型由原來的具體類型,變成參數化的“類型”,有點類似于方法中的變量參數,不過此時是類型定義成參數形式(你可以理解為類型形參),然后在使用時傳入具體的類型(也就是類型實參)。為什么這樣操作呢?因為它能讓類型"參數化",也就是在不創建新的類型的情況下,通過泛型可以指定不同類型來控制形參具體限制的類型。
總結:
泛型介紹完了, 小伙伴看完上述解釋后,能理解泛型是什么了嗎?我們可以用兩句話來概括一下:
泛型在聲明時,用標記符表示,僅僅作為“參數化的類型”,可以理解為形式參數。
比如:
//定義List集合時,用標記符E表示任意類型,E可以理解為形式參數,沒有具體的類型值public interface Listextends Collection{ ----------}
泛型在使用時,需要指定具體的類型,也就是類型實際的參數。
比如:
//在使用List集合時,需要確定E的具體類型,String可以理解為具體的類型,也就是實際的參數值Listlist = new ArrayList();
1.2 常用的泛型標記符
E - Element (集合使用,因集合中存放元素)
T - Type(Java 類)
K - Key(鍵) V - Value(值)
N - Number(數值類型)
? - 表示不確定的java類型
S、U、V - 2nd、3rd、4th types
你可能會有疑問,弄這么多標識符干嘛,直接使用萬能的Object難道不香么?我們知道Object是所有類的基類(任何類如果沒有指明其繼承類,都默認繼承于Object類),因此任何類的對象都可以設置Object的引用,只不過在使用的時候可能要類型強制轉換。但是如果設置了泛型E、T等這些標識符,那么在實際使用之前類型就已經確定,因此不再需要類型強制轉換
2.泛型的語法使用
2.1 泛型在集合中的使用
單列集合中List中
public interface List<E> extends Collection<E> {
----
<T> T[] toArray(T[] a);
----
}
雙列集合Map中
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
---
V get(Object key){---};
V put(K key, V value){---};
V remove(Object key){---};
void putAll(Map<? extends K, ? extends V> m){---};
-----
}
小伙們可以看到:List,HashMap的源碼,在聲明集合時或者定義方法時,使用采用尖括號內加占位符的形式 ,這里的占位符就是我們上面說的泛型標記符,泛型標記符號E,K,V,T等用來表示任意類型(E,K,V,T也就是“泛型形參”,在實例化集合對象時需要明確的具體的類型(也就是“泛型的實際參數”))。
通過觀察集合的源碼,那么我們自己也可以定義泛型接口,泛型類以及泛型方法,下面我們一起操作一下吧。
2.2 聲明泛型接口
泛型應用于接口。例如生成器(GeneratorType),這是一種專門負責創建對象的類。當使用生成器創建新的對象時,它不需要任何參數,也就是說生成器無需額外的信息就知道如何創建新對象。
一般而言,一個生成器只定義一個方法,該方法用以產生新的對象。
/**
* 定義一個泛型接口:生成任意對象
* @param <T>: 泛型形式參數,可以是任意類型
*/
public interface GeneratorType<T> {
T create();
}
/**
* 測試泛型接口
*/
class DemoGeneratorType{
public static void main(String[] args) {
//1.使用生成器:創建random對象
GeneratorType<Random> gt= new GeneratorType<Random>() {
@Override
public Random create() {
return new Random();
}
};
//2.使用 GeneratorType:創建對象
Random random = gt.create();
}
}
來,小伙伴們,我們一起分析下上面的代碼:
我們聲明了一個泛型接口 GeneratorType,目的用來生成任意類型的對象,在這里T可以表示任意類型。
我們在測試類中,通過GeneratorType創建對象時,可以傳遞任意類型。
比如 GeneratorType,那么就可以生成Random對象了
注意: 在這里,我們通過匿名內部類的方式創建了Random對象,這種寫法大家要慢慢熟悉喔。
2.3 聲明泛型類
泛型應用于類上面。例如訂單類(Order),這是一個專門負責封裝訂單里面商品的類,當我們購物生成訂單時,訂單里面可以包含任何商品信息。
請注意,在類上定義的泛型,在類的變量、方法的參數以及方法中同樣也能使用(靜態方法除外)。
/**
* 定義一個訂單類:封裝任意類型的商品信息
* @param <T>
*/
public class Order<T> {
private T t ;//在變量中使用: T表示任意商品類型
public T get(){//在普通方法中使用:T表示任意商品類型
return t;
}
public void set(T t){//在方法的參數使用: T表示任意類型
this.t = t;
}
//測試:
public static void main(String[] args) {
Order<Phone> order = new Order<Phone>();//創建訂單對象:封裝Phone商品
order.set(new Phone("華為Mate20",3899.0));
System.out.println("商品名稱:"+order.get().getPhoneName());
}
}
//定義手機商品類
class Phone{
private String phoneName;
private Double phonePrice;
public Phone(String phoneName, Double phonePrice) {
this.phoneName = phoneName;
this.phonePrice = phonePrice;
}
public Phone() {
}
public String getPhoneName() {
return phoneName;
}
public void setPhoneName(String phoneName) {
this.phoneName = phoneName;
}
public Double getPhonePrice() {
return phonePrice;
}
public void setPhonePrice(Double phonePrice) {
this.phonePrice = phonePrice;
}
}
ok,泛型類我們聲明完成了,大家看一下是不是和我們聲明泛型接口很相似啊,確實是一樣的。
聲明的語法就是:類名,在這里T可以表示任意類型。
小伙伴也可以看到,我們定義了一個帶泛型的Order類,在我們創建訂單對象時,可以傳入任意類型的商品對象,使我們的操作更加靈活
2.4 聲明泛型方法
泛型應用于方法上面。前面說過在泛型類上定義的泛型,在類的方法中也能使用(靜態方法除外)。但是有的時候我們只想在某個方法上使用泛型,而不是整個類,這也是被允許的,下面我和小伙們一起來體驗一下。
比如FactoryBean工廠類,通過泛型方法,創建任意類型的對象。
package cn.qf;
/**
* 定義一個工廠Bean:
*/
public class FactoryBean {
/*
定義不帶泛型的方法
*/
public static Object createObject0(String className) throws Exception{
return Class.forName(className).newInstance();
}
/*
定義一個普通的泛型方法:className表示類的全路徑
*/
public <T> T createObject1(String className) throws Exception{
return (T) Class.forName(className).newInstance();
}
/*
定義一個靜態的泛型方法:className表示類的全路徑
*/
public static <T> T createObject2(String className)throws Exception{
return (E) Class.forName(className).newInstance();
}
//測試:
public static void main(String[] args) throws Exception {
//創建一個Phone對象 : 不使用泛型方法,需要類型強轉
Phone p1 = (Phone) FactoryBean.createObject0("cn.qf.Phone");
//創建一個Phone對象 :泛型方法,不需要類型強轉
Phone p2 = FactoryBean.createObject2("cn.qf.Phone");
}
}
class Phone{
private String phoneName;
private Double phonePrice;
----
}
在這里我們使用工廠模式來創建對象,為了在我們獲取對象時,不用類型強轉,我們也使用了泛型。小伙伴通過代碼可以看到,不使用泛型的方法,在獲取對象時,需要類型強轉(可能會引起類型強轉異常)。
在使用泛型方法獲取對象時,不需要類型強轉(可以避免引起類型強轉異常)。
2.5 泛型方法、泛型接口、泛型類小結
從上面的介紹小伙伴也看到了,泛型類的好處就是在泛型類上定義的泛型,在類的方法中也能使用(普通靜態方法除外)。而泛型方法的最大優點就是能獨立于類,不受類是否是泛型類的限制。因此當你考慮使用泛型的時候,優先考慮定義泛型方法。如果非要定義泛型類,
個人建議通過使用泛型方法來將整個類泛型化,因為這樣就不用擔心靜態方法的事,如果有靜態方法那必然是泛型方法。這樣就能避免普通靜態方法無法獲取泛型類泛型的尷尬局面。
你以為這就把泛型介紹完了嗎?并沒有,小伙伴們先休息片刻,稍后我們繼續喔。
闖關練習
需求:
定義一個泛型類:
包含與類的泛型一樣的變量,
包含與類的泛型一樣的方法,參數也使用泛型
同時定義一個類的泛型不相同的泛型方法
答案:
/**
* 定義泛型類:
* @param <T>: 泛型T
*/
public class GenericDemo4<T> {
//1.定義一個與T 一樣的變量
private T t;
//2.定義一個與T一樣的方法
public T test1(T outer){
System.out.println(outer);
return outer;
}
//3.定義一個與T不一樣的方法
public <E> E test2(E e){
System.out.println("自定義泛型的方法:"+e);
return e;
}
//測試:
public static void main(String[] args) {
//1.創建對象:指定T的泛型為 String
GenericDemo4<String> gt = new GenericDemo4<String>();
//2.調用 與T 一樣的泛型方法
gt.test1("hello world");
//3.調用 與T 不一樣的泛型方法
gt.test2(10);
}
}
開班時間:2021-04-12(深圳)
開班盛況開班時間:2021-05-17(北京)
開班盛況開班時間:2021-03-22(杭州)
開班盛況開班時間:2021-04-26(北京)
開班盛況開班時間:2021-05-10(北京)
開班盛況開班時間:2021-02-22(北京)
開班盛況開班時間:2021-07-12(北京)
預約報名開班時間:2020-09-21(上海)
開班盛況開班時間:2021-07-12(北京)
預約報名開班時間:2019-07-22(北京)
開班盛況Copyright 2011-2023 北京千鋒互聯科技有限公司 .All Right 京ICP備12003911號-5 京公網安備 11010802035720號