日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

[转载] JAVA泛型杂谈--擦除,协变,逆变,通配符等

發(fā)布時(shí)間:2025/3/11 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [转载] JAVA泛型杂谈--擦除,协变,逆变,通配符等 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

參考鏈接: Java中的協(xié)變返回類型

在《JAVA核心思想》這本書里,關(guān)于泛型的章節(jié)意外的很多,小小的泛型里其實(shí)有很多可以學(xué)習(xí)的內(nèi)容,我總結(jié)下最近看書的成果。?

一. 泛型的好處和應(yīng)用?

最基礎(chǔ)的用到泛型的地方無非是在容器里 使用泛型用以保證容器內(nèi)數(shù)據(jù)的類型統(tǒng)一,所以我們先總結(jié)下泛型使用的好處:?

可以統(tǒng)一集合類型容器的存儲類型,防止在運(yùn)行期出現(xiàn)類型裝換異常,增加編譯時(shí)類型的檢查解決重復(fù)代碼的編寫,能夠復(fù)用算法。可以起到重載的作用?

第二個(gè)作用很好理解,泛型 的 ‘泛’ 即代表著 ‘泛化’,不僅僅是保證容器的安全性,更重要的是減少類型的限制,讓我們編寫更加通用的代碼。我們舉個(gè)例子:? 在javaweb中,我們經(jīng)常向前臺返回JSON對象信息,不同的業(yè)務(wù)可以會組裝不同的bean,為了節(jié)省提高代碼的復(fù)用性,我們可以這么寫一個(gè)類:?

public class ReturnObject<A, B> {

?

? ? public final A a;

? ? public final B b;

?

? ? public ReturnObject(A a, B b) {

? ? ? ? this.a = a;

? ? ? ? this.b = b;

? ? }

?

? ? public <A> A t(A a) {

? ? ? ? return a;

? ? }

?

? ? @Override

? ? public String toString() {

? ? ? ? return "ReturnObject{" +

? ? ? ? ? ? ? ? "a=" + a +

? ? ? ? ? ? ? ? ", b=" + b +

? ? ? ? ? ? ? ? '}';

? ? }

}?

這個(gè)類沒有具體的類型,意思也很簡單,就是不限定變量的類型,根據(jù)你業(yè)務(wù)的不同,你可以傳不同的類型進(jìn)去(通過構(gòu)造器),更方便的是,java泛型支持繼承,你可以隨意拓展你的業(yè)務(wù)字段,也就不再需要為了一種業(yè)務(wù)專門創(chuàng)建一個(gè)bean類了。?

// 業(yè)務(wù)字段拓展

public class ReturnObjectExtender<A, B, C> extends ReturnObject<A, B> {

?

? ? public final C c;

?

? ? public ReturnObjectExtender(A a, B b, C c) {

? ? ? ? super(a, b);

? ? ? ? this.c = c;

? ? }

?

? ? @Override

? ? public String toString() {

? ? ? ? return "ReturnObjectExtender{" +

? ? ? ? ? ? ? ? "c=" + c +

? ? ? ? ? ? ? ? ", a=" + a +

? ? ? ? ? ? ? ? ", b=" + b +

? ? ? ? ? ? ? ? '}';

? ? }

}?

二. 重要!泛型的擦除?

JAVA的泛型都是通過擦除來實(shí)現(xiàn)的,這句話的意思是 當(dāng)你的程序真正跑起來的時(shí)候,任何具體的類型其實(shí)都已經(jīng)被擦除了,所以在下面的例子中,輸出的結(jié)果是true,aClass和aClass1都是一樣的class生成的對象。?

Class aClass = new ArrayList<Integer>().getClass();

Class aClass1 = new ArrayList<String>().getClass();

?

System.out.println(aClass == aClass1);

?

擦除的負(fù)面效應(yīng)直接體現(xiàn)在如果你寫下面這段代碼,T類型并不能認(rèn)出你傳給它的是String類型,T直接會被Object替代,?

public class WildCardTest<T> {

?

? ? public void f(T t) {

? ? //這一段編譯報(bào)錯(cuò)

? ? ? ? t.isEmpty();

? ? }

?

? ? public static void main(String[] args) {

? ? ? ? WildCardTest<String> stringWildCardTest = new WildCardTest<>();

? ? ? ? stringWildCardTest.f("");

? ? }

}?

要讓String調(diào)用它的isEmpty()方法,需要給泛型一個(gè)邊界,代碼只需要重新改一下,T extends String表明T可以是String類或是String的子類,如果傳入的沒有問題,那就可以調(diào)用isEmpty()方法。?

public class WildCardTest<T extends String> {

?

? ? public void f(T t) {

? ? ? ? t.isEmpty();

? ? }

?

? ? public static void main(String[] args) {

? ? ? ? WildCardTest<String> stringWildCardTest = new WildCardTest<>();

? ? ? ? stringWildCardTest.f("");

? ? }

}?

擦除是歷史遺留問題?

java的泛型不是從jdk1.0就出現(xiàn)的,為了跟以往沒有泛型代碼的源代碼兼容,例如List被擦除為List,而普通的類型變量在未指定邊界的時(shí)候被擦除為Object,從而實(shí)現(xiàn)泛型的功能并且向后兼容。?

三. 泛型的通配符(逆變與協(xié)變)?

這是兩個(gè)賦值,一個(gè)是數(shù)組,ArrayList因?yàn)槭荂ollection的子類,所以數(shù)組向上轉(zhuǎn)型是可以的;另一個(gè)是帶泛型的ArrayList,帶ArrayList的泛型并不能賦值給帶Collection的泛型。?

Collection[] collections = new ArrayList[]{};

//泛型會報(bào)錯(cuò)

ArrayList<Collection> collections1 = new ArrayList<ArrayList>();?

逆變,協(xié)變與不變?

為什么會導(dǎo)致這樣的差異呢?這里又引出了一個(gè)概念– 逆變,協(xié)變與不變。逆變與協(xié)變用來描述類型轉(zhuǎn)換后的繼承關(guān)系。?

f(?)是逆變(contravariant)的,當(dāng)A≤B時(shí)有f(B)≤f(A)成立;

f(?)是協(xié)變(covariant)的,當(dāng)A≤B時(shí)有成立f(A)≤f(B)成立;

f(?)是不變(invariant)的,當(dāng)A≤B時(shí)上述兩個(gè)式子均不成立,即f(A)與f(B)相互之間沒有繼承關(guān)系。?

根據(jù)上面兩個(gè)賦值語句做解釋,A 是ArrayList ,B是Collection,所以B > A,這個(gè)沒有問題,然后 A[] 數(shù)組當(dāng)作f(A),B[] 數(shù)組當(dāng)作f(B),并且A[]可以賦值給B[]數(shù)組,說明 f(B) >=f(A),符合協(xié)變原則,所以數(shù)組是協(xié)變的。? 而泛型也套用這個(gè)規(guī)則,發(fā)現(xiàn) 泛型其實(shí)是不變的。?

Java中泛型是不變的,可有時(shí)需要實(shí)現(xiàn)逆變與協(xié)變,怎么辦呢?這時(shí),通配符?派上了用場:?

// <? extends>實(shí)現(xiàn)了泛型的協(xié)變,它意味著某種Collection或Collection子類的類型,規(guī)定了該容器持有類型的上限,它是具體的類型,只是這個(gè)類型是什么沒有人關(guān)心? 比如:

List<? extends Collection> list = new ArrayList<ArrayList>();

?

// <? super>實(shí)現(xiàn)了泛型的逆變,它意味著某種ArrayList或ArrayList父類的類型,規(guī)定了該容器持有類型的下限,比如:

List<? super ArrayList> list = new ArrayList<Collection>();??

逆變,協(xié)變的應(yīng)用?

在協(xié)變中,還是先以數(shù)組做舉例,協(xié)變中對持有對象的存入有嚴(yán)格限制:?

Collection[] collections = new ArrayList[2];

?

collections[0] = new ArrayList();

//這一步編譯沒問題。但是運(yùn)行時(shí)發(fā)現(xiàn)數(shù)組里已經(jīng)定好了只能存ArrayList類型,所以會拋ArrayStoreException

collections[1] = new LinkedList();?

所以在泛型的協(xié)變中,例如ArrayList的add方法是不能調(diào)用了,在編譯期間直接報(bào)錯(cuò)。?

這是ArrayList 的add,get方法定義,當(dāng)使用協(xié)變時(shí),E e 會被直接替換成 ? extends E?

public boolean add(E e);

public E get(int index);?

具體事例:?

ArrayList<? extends Set> sets = new HashSet<>();

?

//因?yàn)?? extends Set 編譯器不知道sets引用指向什么對象,有可能是 HashSet,可能是TreeSet,這種不確定性導(dǎo)致sets不能使用add方法。

//sets.add(new HashSet());

?

//能插入null值

sets.add(null);

?

//因?yàn)檫@個(gè)泛型參數(shù)的上限是Set,為了安全性,所以只返回set類型

Set set = sets.get(0);?

在逆變中,因?yàn)橐?guī)定了泛型的下界,所以get set 方法的使用限制又有所不同:?

ArrayList<? super List> list = new ArrayList<Collection>();

?

//對于 add方法,只能放List或List的子類,因?yàn)閘ist容器泛型參數(shù)都是List的父類 不會出現(xiàn)問題

list.add(new ArrayList());

//不能add HashSet

//list.add(new HashSet());

//不能add Object

//list.add(new Object());

?

//因?yàn)檫@個(gè)泛型參數(shù)的下限是List? ? 所以無法確定這是個(gè)什么類型,只能返回Object

Object object = list.get(0);?

無界通配符?

除了extends和super,還有一種 List<\?>這種通配符,代表著任何事物,但它與List不同的是:?

List<\Object> = List = ‘持有任何Object類型的原生List’List<\?>表示–’具有某種特定類型的非原生List,只是我們不知道那種類型是什么’。?

具體用代碼看出區(qū)別:?

ArrayList<Collection> collections2 = new ArrayList<>();

?

ArrayList<?> objects = new ArrayList<>();

//?代表持有某種特定類型 ,所以也可以是Collection,這種賦值時(shí)合法的

objects = collections2;

?

//?代表持有某種特定類型,d但是不確定具體哪種,所以只能返回Object

Object o = objects.get(0);

?

//?代表持有某種特定類型,但是什么類型編譯器并不知道,所以為了安全起見,不會讓你用add方法

//objects.add(new Object());

?

?

//可以add null

objects.add(null);

?

總結(jié)來說:?

要從泛型類取數(shù)據(jù)時(shí),用extends;要往泛型類寫數(shù)據(jù)時(shí),用super;既要取又要寫,就不用通配符(即extends與super都不用)。?

四. 總結(jié)?

這篇文章總結(jié)的是書里比較重要的知識點(diǎn),跳過了簡單的應(yīng)用,寫了那么多,感覺總結(jié)還是很有必要的,你第一遍看書也許概念會有些懵懂,但是再記錄總結(jié)下,你會解開第一遍看書時(shí)有點(diǎn)么棱兩可的知識點(diǎn)。

總結(jié)

以上是生活随笔為你收集整理的[转载] JAVA泛型杂谈--擦除,协变,逆变,通配符等的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。