前言
在上一篇文章中,給大家講解了泛型的概念、作用、使用場景,以及泛型集合、泛型介面和泛型類的用法,但受限于篇幅,并沒有把泛型的內容講解完畢,所以今天我們會繼續學習泛型方法、泛型擦除,以及通配符等的內容,希望大家繼續做好學習的準備哦,
全文大約【4600】 字,不說廢話,只講可以讓你學到技術、明白原理的純干貨!本文帶有豐富的案例及配圖視頻,讓你更好地理解和運用文中的技術概念,并可以給你帶來具有足夠啟迪的思考...
一. 泛型方法
1. 簡介
我們可以在定義介面和類時使用泛型,這樣該介面和類中的所有方法及成員變數等處,也都可以使用該泛型,但其實泛型可以應用在整個類上,也可以只應用在類中的某個方法上,也就是說,方法所在的類可以是泛型類,也可以不是泛型類,方法中是否帶有泛型,與其所在的類有沒有泛型沒有關系,
泛型方法是在呼叫方法時才確定型別的方法,泛型可以使得該方法獨立于類而產生變化,另外,static靜態方法無法訪問泛型類的型別引數,因此,如果想讓一個static方法具有泛型能力,就必須使該靜態方法成為泛型方法,
2. 語法
我們在定義泛型方法時,需要在方法名的前面添加型別引數,定義泛型方法的語法格式如下:
[訪問權限修飾符] [static] [final] <型別引數串列> 回傳值型別 方法名([形式引數串列])
例如:
public static <T> List showInfo(Class<T> clazz,int userId){}
一般情況下,我們撰寫泛型方法時,必須在該方法的名稱前宣告要使用的泛型,并且可以同時宣告多個泛型,中間也是用逗號分割,接下來就定義一個泛型方法,給大家具體介紹一下泛型方法的創建和使用,
3. 代碼案例
這里我們定義一個泛型方法,用于對陣列排序后再遍歷元素輸出,代碼如下:
import java.util.Arrays;
public class Demo04 {
//定義了一個靜態的泛型方法,遍歷陣列中的每個元素
public static <T> void printArray(T[] arr) {
//先對陣列進行排序
Arrays.sort(arr);
//再遍歷陣列元素
for (T t : arr) {
System.out.print(t + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] nums= {100,39,8,200,65};
//呼叫泛型方法
printArray(nums);
}
}
在上面的代碼中,printArray()就是一個泛型方法,該方法中使用了型別引數T,并且我們在方法的引數中,使用型別引數T定義了一個泛型陣列T[],接著對該陣列進行排序和遍歷,這樣以后無論我們傳入任何型別的陣列,都可以在不進行型別轉換的前提下,輕松實作排序等功能了,這樣我們之前提的需求也就很容易實作了,
二. 通配符
除了以上這些用法之外,泛型中還有一個很重要的通配符功能,接下來我們就來看看它是怎么回事,
1. 簡介
泛型中的通配符其實也是一種特殊的泛型型別,也稱為通配符型別引數,利用通配符型別引數,可以讓我們撰寫出更通用的代碼,甚至可以在不知道實際型別的情況下使用它們,我們一般是使用 ? 來代替具體的型別引數,例如 List<?> 在邏輯上可以等同于 List
對此,有的小伙伴可能會很好奇,我們為什么需要通配符呢?其實之所以會出現通配符,主要是在開發時,有時候我們需要一個泛型型別,但我們卻不知道該使用哪個具體的型別,在這種情況下,我們就可以使用通配符型別引數,讓代碼更加地通用,比如,我們想撰寫一個可以接受任何型別的集合,并回傳其中最大的元素時,此時我們可能并不確定到底該傳入哪個具體的集合,那使用通配符就會更好一些,
2. 通配符的形式
泛型通配符在具體使用時,有如下三種實作形式:
- 未限定通配符(?) : ?表示未知型別的通配符;
- 上限通配符(? extends T) : ?表示型別上限的通配符,T是一個類或介面;
- 下限通配符(? super T) : ?表示型別下限的通配符,T是一個類或介面,
接下來我們針對以上這三種形式,分別通過幾個案例來給大家講解其用法,
3. 未限定通配符(?)
未限定通配符(?)是一種表示未知型別的通配符,它可以在需要一個型別引數的情況下使用,但由于沒有限制,因此它只能用于簡單的情況,例如集合中的迭代器或者回傳型別是泛型的方法等,下面是一個簡單的例子:
import java.util.ArrayList;
import java.util.List;
public class Demo05 {
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
List<Integer> ages = new ArrayList<Integer>();
List<Number> numbers = new ArrayList<Number>();
names.add("一一哥");
names.add("秦始皇");
ages.add(28);
ages.add(50);
ages.add(28);
numbers.add(100);
numbers.add(800);
printElement(names);
printElement(ages);
printElement(numbers);
}
//未限定通配符的使用
public static void printElement(List<?> data) {
for(int i=0;i<data.size();i++) {
//data.getClass().getSimpleName():用于獲取某個類的類名
System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
}
}
}
在這個例子中,printElement()方法就接受了一個未知型別的List集合,所以names,ages,numbers都可以作為這個方法的實參,這就是未限定通配符的作用,
4. PECS原則
PECS是Producer Extends Consumer Super的縮寫,這是關于Java泛型的一種設計原則,該原則表示,如果我們需要回傳T,它是生產者(Producer),要使用extends通配符;如果需要寫入T,它就是消費者(Consumer),要使用super通配符,該原則可以指導我們在使用泛型時,該如何定義型別引數的上限和下限,
當我們使用泛型時,可能需要定義型別引數的上限和下限,
例如,我們想要撰寫一個方法來處理一些集合型別,但我們不知道這些集合中到底有什么型別的元素,此時我們就可以定義一個型別引數來處理所有的集合型別,一般我們可以利用extends來設定泛型上限,利用super來設定泛型下限,接下來會在下面的第5和第6小結中,給大家講解泛型的上限和下限具體該如何實作,請大家繼續往下學習,
5. 上限通配符(? extends T)
上限通配符(?extends T)是一種表示型別上限的通配符,其中T是一個類或介面,泛型類的型別必須實作或繼承 T這個介面或類,它指定了可以使用的型別上限,主要是用于限制輸入的引數型別,
import java.util.ArrayList;
import java.util.List;
/**
* @author 一一哥Sun
*/
public class Demo06 {
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
List<Integer> ages = new ArrayList<Integer>();
List<Number> numbers = new ArrayList<Number>();
names.add("一一哥");
names.add("秦始皇");
ages.add(28);
ages.add(50);
ages.add(28);
numbers.add(100);
numbers.add(800);
//String等非Number型別就不行
//printElementUpbound(names);
//泛型值只能是Number及其子型別別,所以Integer/Double/Float等型別都可以,但String就不行
printElementUpbound(ages);
printElementUpbound(numbers);
}
//上限通配符的使用,這里的泛型值只能是Number及其子型別別
public static void printElementUpbound(List<? extends Number> data) {
for(int i=0;i<data.size();i++) {
//data.getClass().getSimpleName():用于獲取某個類的類名
System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
}
}
}
在這個例子中,printElementUpbound方法中的集合泛型,可以是Number類或其子類,除此之外的其他型別都不行,也就是說,我們只能使用Number或其子類作為型別引數,泛型型別的上限是Number,這就是上限通配符的含義,
6. 下限通配符(? super T)
下限通配符(?super T)是一種表示型別下限的通配符,其中T是一個類或介面,它指定了可以使用的型別下限,主要用于限制輸出的引數型別,下面是一個簡單的例子:
import java.util.ArrayList;
import java.util.List;
public class Demo07 {
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
List<Integer> ages = new ArrayList<Integer>();
List<Double> numbers = new ArrayList<Double>();
names.add("一一哥");
names.add("秦始皇");
ages.add(28);
ages.add(50);
ages.add(28);
numbers.add(100.0);
numbers.add(800.9);
//String等非Number型別就不行
//printElementUpbound(names);
//此時Double型別也不行
//printElementDownbound(numbers);
//泛型值只能是Integer及其父型別別,所以Double/Float/String等型別都不可以
printElementDownbound(ages);
}
//下限通配符的使用,這里的泛型值只能是Integer及其父型別別
public static void printElementDownbound(List<? super Integer> data) {
for(int i=0;i<data.size();i++) {
System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i));
}
}
}
在這個例子中,printElementDownbound方法中的集合泛型,可以是Integer或其父型別,即型別下限是Integer,除此之外的其他型別都不行,也就是說,我們只能使用Integer或其父類作為型別引數,泛型型別的下限是Integer,這就是下限通配符的含義,
7. <? extends T>和<? super T>的區別
在這里,要給大家再總結一下<? extends T>和<? super T>的區別:
- <? extends T> 允許呼叫 T get()這樣的 讀方法來獲取 T物件 的參考,但不允許呼叫 set(T)這樣的 寫方法來傳入 T 的參考(傳入 null 除外);
- <? super T> 允許呼叫 set(T)這樣的 寫方法傳入 T物件 的參考,但不允許呼叫 T get()這樣的 讀方法來獲取 T物件 的參考(獲取 Object 除外),
- 即 <? extends T> 允許讀不允許寫, <? super T> 允許寫不允許讀,
大家注意,無論是未限定通配符、上限通配符還是下限通配符,我們既可以在方法中使用,也可以在類或介面中使用,
三. 泛型擦除
我們在學習泛型時,除了要掌握上面這些泛型類、泛型介面、泛型方法以及通配符等內容之外,還要學習泛型擦除的相關內容,那么什么是泛型擦除呢?我們繼續往下學習吧,
1. 簡介
所謂的泛型擦除(Type Erasure),就是指在編譯時,JVM編譯器會將所有的泛型資訊都擦除掉,變成原始型別,一般是將泛型的型別引數替換成具體型別的上限或下限(如果沒有指定上界,則默認為Object),
換句話說,雖然我們在代碼中使用了泛型,但在編譯后,所有的泛型型別都會被擦除掉,轉而使用其對應的原始型別,這就是Java泛型的底層實作原理,這樣設計的目的是為了兼容舊的JDK版本,使得Java具有了較好的向后兼容性,舊的非泛型代碼可以直接使用泛型類別庫,而不需要進行任何修改,同時,Java也提供了反射機制來操作泛型型別,使得泛型型別在某些情況下還是可以被獲取到的,所以即使有泛型擦除,仍然也不會太影響Java虛擬機的運行時效率,
比如,在我們定義一個泛型類時,我們會使用泛型型別引數來代替具體的型別,好比下面這個例子:

2. 泛型擦除帶來的限制
在編譯之后,這個泛型類的型別引數T就會被擦除,成為其對應的原始型別Object,
這也意味著,我們無法在運行時獲取到泛型的實際型別引數,所以泛型擦除的使用會有一些限制,首先由于泛型型別引數被擦除了,因此我們在運行時就無法獲得泛型型別引數的資訊,例如,如果我們有一個List
無法使用基本型別實體化型別引數;
無法在運行時獲取泛型型別資訊;
泛型型別引數不能用于靜態變數或靜態方法;
不能實體化T型別,
接下來再給大家具體分析一下這些限制
2.1 無法使用基本型別實體化型別引數
Java泛型中的型別引數不能是基本型別,只能是類或介面型別,例如,以下代碼在編譯階段會出錯,無法通過編譯:
List<int> list = new ArrayList<int>();
正確的寫法是使用基本型別對應的包裝型別,如下所示:
List<Integer> list = new ArrayList<Integer>();
2.2 無法在運行時獲取泛型型別資訊
由于泛型擦除的存在,導致我們在程式運行時無法獲取泛型型別的資訊,例如,以下代碼在運行時就無法獲取List的元素型別:
List<String> list = new ArrayList<String>();
Class<?> clazz = list.getClass();
Type type = clazz.getGenericSuperclass();
// 輸出:class java.util.ArrayList<E>
System.out.println(type);
在輸出的結果中,我們只能得到ArrayList的型別資訊,而無法獲取到集合中具體的泛型型別資訊,也就是獲取不到String的資訊,但如果我們就是想在運行時獲取到泛型的實際型別引數,其實可以參考以下方式進行實作:
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
public Class<?> getContentType() {
return content.getClass();
}
}
在上面的代碼中,我們新增了一個方法 getContentType(),該方法用于回傳泛型型別引數的實際位元組碼型別,以后我們通過呼叫這個方法,就可以間接地獲取到泛型型別的資訊了,
2.3 泛型型別引數不能用于靜態變數或靜態方法
由于泛型型別引數是在實體化物件時才被確定的,因此不能在靜態變數或靜態方法中使用泛型型別引數,例如,以下代碼是無法編譯通過的:
public class MyClass<T> {
//這樣的代碼編譯不通過
private static T value;
public static void setValue(T value) {
MyClass.value = https://www.cnblogs.com/qian-fen/archive/2023/06/02/value;
}
}
正確的寫法是使用一個實際型別來代替泛型型別引數:
public class MyClass {
private static String value;
public static void setValue(String value) {
MyClass.value = https://www.cnblogs.com/qian-fen/archive/2023/06/02/value;
}
}
2.4 不能實體化T型別
比如在下面的案例中:
public class MyClass<T> {
private T first;
private T last;
public MyClass() {
first = new T();
last = new T();
}
}
上述代碼無法通過編譯,因為構造方法的兩行陳述句:
first = new T();
last = new T();
擦拭后實際上變成了:
first = new Object();
last = new Object();
這樣一來,創建new MyClass<String>()和創建new MyClass<Integer>()就變成了Object,編譯器會阻止這種型別不對的代碼,如果我們想對泛型T進行實體化,需要借助Class< T >引數并集合反射技術來實作,且在使用時也必須傳入Class< T >,
四. 結語
不過,盡管泛型擦除有一些限制,但泛型仍然不失為一種強大的編程工具,它可以提高代碼的可讀性和可維護性,通過合理地使用泛型,我們可以在編譯時進行型別檢查,避免型別轉換的錯誤和運行時例外,從而提高了代碼的安全性和可靠性,同時,我們也需要了解Java泛型擦除的限制,以便在實際應用中做出正確的決策,
視頻教程:視頻教程,戳我直達
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554133.html
標籤:其他
上一篇:celery筆記二之建立celery專案、配置及幾種加載方式
下一篇:返回列表
