Java中的泛型詳解
所謂泛型:就是允許在定義類(lèi)、接口指定類(lèi)型形參,這個(gè)類(lèi)型形參在將在聲明變量、創(chuàng)建對(duì)象時(shí)確定(即傳入實(shí)際的類(lèi)型參數(shù),也可稱(chēng)為類(lèi)型實(shí)參)
泛型類(lèi)或接口
“菱形”語(yǔ)法
//定義
public interface List<E> extends Collection<E>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
//使用
List<String> list = new ArrayList();
//Java7以后可以省略后面尖括號(hào)的類(lèi)型參數(shù)
List<String> list = new ArrayList<>();
從泛型類(lèi)派生子類(lèi)
//方式1
public class App extends GenericType<String>
//方式2
public class App<T> extends GenericType<T>
//方式3
public class App extends GenericType
偽泛型
不存在真正的泛型類(lèi),泛型類(lèi)對(duì)Java虛擬機(jī)來(lái)說(shuō)是透明的.JVM并不知道泛型類(lèi)的存在,換句話來(lái)說(shuō),JVM處理泛型類(lèi)和普通類(lèi)沒(méi)什么區(qū)別的.因此在靜態(tài)方法、靜態(tài)初始化塊、靜態(tài)變量里面不允許使用類(lèi)型形參。
- 以下方式都是錯(cuò)誤的
private static T data;
static{
T f;
}
public static void func(){
T name = 1;
}
下面的例子可以從側(cè)面驗(yàn)證不存在泛型類(lèi)
public static void main(String[] args){
List<String> a1 = new ArrayList<>();
List<Integer> a2 = new ArrayList<>();
System.out.println(a1.getClass() == a2.getClass());
System.out.println(a1.getClass());
System.out.println(a2.getClass());
}
輸出
true
class java.util.ArrayList
class java.util.ArrayList
類(lèi)型通配符
首先必須明確一點(diǎn),假如Foo是Bar的父類(lèi),但是List<Foo>并不是List<Bar>的父類(lèi).為了表示各種泛型的父類(lèi),Java使用"?"來(lái)表示泛型通配.即List<?>來(lái)表示各種泛型List的父類(lèi).帶這種通配符List泛型不能設(shè)置(set)元素,只能獲?。╣et)元素。因?yàn)槌绦驘o(wú)法確定List中的類(lèi)型,所以不能添加對(duì)象。但獲取的對(duì)象肯定是Object類(lèi)型。
以下方法會(huì)編譯出錯(cuò):
List<?> list = new ArrayList<>();
list.add(new Object());
主意幾點(diǎn):
1.List<String>對(duì)象不能被當(dāng)成List<Object>對(duì)象使用,也就是說(shuō):List<String>類(lèi)并不是List<Object>類(lèi)的子類(lèi)。
2.數(shù)組和泛型有所不同:假設(shè)Foo是Bar的一個(gè)子類(lèi)型(子類(lèi)或者子接口),那么Foo[]依然是Bar[]的子類(lèi)型;但G<Foo>不是G<Bar>的子類(lèi)型。
3.為了表示各種泛型List的父類(lèi),我們需要使用類(lèi)型通配符,類(lèi)型通配符是一個(gè)問(wèn)號(hào)(?),將一個(gè)問(wèn)號(hào)作為類(lèi)型實(shí)參傳給List集合,寫(xiě)作:List<?>(意思是未知類(lèi)型元素的List)。這個(gè)問(wèn)號(hào)(?)被稱(chēng)為通配符,它的元素類(lèi)型可以匹配任何類(lèi)型。
通配符的上限
List<? extends SuperType>表示所有SuperType泛型List的父類(lèi)或本身。帶有通配符上限的泛型不能有set方法,只能有g(shù)et方法。
設(shè)置通配符上限能解決如下問(wèn)題:Dog是Animal子類(lèi),有個(gè)getSize方法要獲取傳入List的個(gè)數(shù),代碼如下
abstract class Animal {
public abstract void run();
}
class Dog extends Animal {
public void run() {
System.out.println("Dog run");
}
}
public class App {
public static void getSize(List<Animal> list) {
System.out.println(list.size());
}
public static void main(String[] args) {
List<Dog> list = new ArrayList<>();
getSize(list); // 這里編譯報(bào)錯(cuò)
}
}
這里編程出錯(cuò)的原因是List<Animal>并不是List<Dog>的父類(lèi)。解決方案一可以把getSize方法中形參List<Animal>改為L(zhǎng)ist<?>,不過(guò)這樣的話在每次get對(duì)象的時(shí)候都要強(qiáng)制類(lèi)型轉(zhuǎn)換,比較麻煩。使用通配符上限很好的解決了這個(gè)問(wèn)題,可以把List<Animal>改為L(zhǎng)ist<? extends Animal>,編譯就不會(huì)錯(cuò)了,也不用類(lèi)型轉(zhuǎn)換。
通配符的下限
List<? super SubType>表示SubType泛型List的下限。帶有通配符上限的泛型不能有g(shù)et方法,只能有set方法。
泛型方法
如果定義類(lèi)、接口是沒(méi)有使用類(lèi)型形參,但定義方法時(shí)想自己定義類(lèi)型形參,這也是可以的,JDK1.5還提供了泛型方法的支持。泛型方法的方法簽名比普通方法的方法簽名多了類(lèi)型形參聲明,類(lèi)型形參聲明以尖括號(hào)括起來(lái),多個(gè)類(lèi)型形參之間以逗號(hào)(,)隔開(kāi),所有類(lèi)型形參聲明放在方法修飾符和方法返回值類(lèi)型之間.語(yǔ)法格式如下:
修飾符 返回值類(lèi)型 方法名(類(lèi)形列表){
//方法體
}
泛型方法允許類(lèi)型形參被用來(lái)表示方法的一個(gè)或多個(gè)參數(shù)之間的類(lèi)型依賴(lài)關(guān)系,或者方法返回值與參數(shù)之間的類(lèi)型依賴(lài)關(guān)系。如果沒(méi)有這樣的類(lèi)型依賴(lài)關(guān)系,就不應(yīng)該使用泛型方法。Collections的copy方法就使用泛型方法:
public static <T> void copy(List<? super T> dest, List<? extends T> src){ ...}
這個(gè)方法要求src類(lèi)型必須是dest類(lèi)型的子類(lèi)或本身。
擦除和轉(zhuǎn)換
在嚴(yán)格的泛型代碼里,帶泛型聲明的類(lèi)總應(yīng)該帶著類(lèi)型參數(shù)。但為了與老的Java代碼保持一致,也允許在使用帶泛型聲明的類(lèi)時(shí)不指定類(lèi)型參數(shù)。如果沒(méi)有為這個(gè)泛型類(lèi)指定類(lèi)型參數(shù),則該類(lèi)型參數(shù)被稱(chēng)作一個(gè)raw type(原始類(lèi)型),默認(rèn)是該聲明該參數(shù)時(shí)指定的第一個(gè)上限類(lèi)型。
當(dāng)把一個(gè)具有泛型信息的對(duì)象賦給另一個(gè)沒(méi)有泛型信息的變量時(shí),則所有在尖括號(hào)之間的類(lèi)型信息都被扔掉了。比如說(shuō)一個(gè)List<String>類(lèi)型被轉(zhuǎn)換為L(zhǎng)ist,則該List對(duì)集合元素的類(lèi)型檢查變成了成類(lèi)型變量的上限(即Object),這種情況被為擦除。
示例
class Apple<T extends Number>
{
T size;
public Apple()
{
}
public Apple(T size)
{
this.size = size;
}
public void setSize(T size)
{
this.size = size;
}
public T getSize()
{
return this.size;
}
}
public class ErasureTest
{
public static void main(String[] args)
{
Apple<Integer> a = new Apple<>(6); // ①
// a的getSize方法返回Integer對(duì)象
Integer as = a.getSize();
// 把a(bǔ)對(duì)象賦給Apple變量,丟失尖括號(hào)里的類(lèi)型信息
Apple b = a; // ②
// b只知道size的類(lèi)型是Number
Number size1 = b.getSize();
// 下面代碼引起編譯錯(cuò)誤
Integer size2 = b.getSize(); // ③
}
}
相關(guān)文章
SpringBoot使用AOP實(shí)現(xiàn)防重復(fù)提交功能
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何使用AOP實(shí)現(xiàn)防重復(fù)提交功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03Java8 自定義CompletableFuture的原理解析
這篇文章主要介紹了Java8 自定義CompletableFuture的原理解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析
這篇文章主要介紹了SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析,本篇文章簡(jiǎn)單介紹?OpenFeign?調(diào)用傳遞?header?,以及多線程環(huán)境下可能會(huì)出現(xiàn)的問(wèn)題,其中涉及到?ThreadLocal?的相關(guān)知識(shí),需要的朋友可以參考下2022-07-07visual studio 2019安裝配置可編寫(xiě)c/c++語(yǔ)言的IDE環(huán)境
這篇文章主要介紹了visual studio 2019安裝配置可編寫(xiě)c/c++語(yǔ)言的IDE環(huán)境,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03