[轉載] JAVA泛型雜談--擦除,協變,逆變,通配符等

參考鏈接: Java中的協變返回類型

在《JAVA核心思想》這本書里,關于泛型的章節意外的很多,小小的泛型里其實有很多可以學習的內容,我總結下最近看書的成果。?

一. 泛型的好處和應用?

最基礎的用到泛型的地方無非是在容器里 使用泛型用以保證容器內數據的類型統一,所以我們先總結下泛型使用的好處:?

可以統一集合類型容器的存儲類型,防止在運行期出現類型裝換異常,增加編譯時類型的檢查解決重復代碼的編寫,能夠復用算法。可以起到重載的作用?

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

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 +

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

? ? }

}?

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

// 業務字段拓展

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的泛型都是通過擦除來實現的,這句話的意思是 當你的程序真正跑起來的時候,任何具體的類型其實都已經被擦除了,所以在下面的例子中,輸出的結果是true,aClass和aClass1都是一樣的class生成的對象。?

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

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

?

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

?

擦除的負面效應直接體現在如果你寫下面這段代碼,T類型并不能認出你傳給它的是String類型,T直接會被Object替代,?

public class WildCardTest<T> {

?

? ? public void f(T t) {

? ? //這一段編譯報錯

? ? ? ? t.isEmpty();

? ? }

?

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

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

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

? ? }

}?

要讓String調用它的isEmpty()方法,需要給泛型一個邊界,代碼只需要重新改一下,T extends String表明T可以是String類或是String的子類,如果傳入的沒有問題,那就可以調用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就出現的,為了跟以往沒有泛型代碼的源代碼兼容,例如List被擦除為List,而普通的類型變量在未指定邊界的時候被擦除為Object,從而實現泛型的功能并且向后兼容。?

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

這是兩個賦值,一個是數組,ArrayList因為是Collection的子類,所以數組向上轉型是可以的;另一個是帶泛型的ArrayList,帶ArrayList的泛型并不能賦值給帶Collection的泛型。?

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

//泛型會報錯

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

逆變,協變與不變?

為什么會導致這樣的差異呢?這里又引出了一個概念– 逆變,協變與不變。逆變與協變用來描述類型轉換后的繼承關系。?

f(?)是逆變(contravariant)的,當A≤B時有f(B)≤f(A)成立;

f(?)是協變(covariant)的,當A≤B時有成立f(A)≤f(B)成立;

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

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

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

// <? extends>實現了泛型的協變,它意味著某種Collection或Collection子類的類型,規定了該容器持有類型的上限,它是具體的類型,只是這個類型是什么沒有人關心? 比如:

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

?

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

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

逆變,協變的應用?

在協變中,還是先以數組做舉例,協變中對持有對象的存入有嚴格限制:?

Collection[] collections = new ArrayList[2];

?

collections[0] = new ArrayList();

//這一步編譯沒問題。但是運行時發現數組里已經定好了只能存ArrayList類型,所以會拋ArrayStoreException

collections[1] = new LinkedList();?

所以在泛型的協變中,例如ArrayList的add方法是不能調用了,在編譯期間直接報錯。?

這是ArrayList 的add,get方法定義,當使用協變時,E e 會被直接替換成 ? extends E?

public boolean add(E e);

public E get(int index);?

具體事例:?

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

?

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

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

?

//能插入null值

sets.add(null);

?

//因為這個泛型參數的上限是Set,為了安全性,所以只返回set類型

Set set = sets.get(0);?

在逆變中,因為規定了泛型的下界,所以get set 方法的使用限制又有所不同:?

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

?

//對于 add方法,只能放List或List的子類,因為list容器泛型參數都是List的父類 不會出現問題

list.add(new ArrayList());

//不能add HashSet

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

//不能add Object

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

?

//因為這個泛型參數的下限是List? ? 所以無法確定這是個什么類型,只能返回Object

Object object = list.get(0);?

無界通配符?

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

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

具體用代碼看出區別:?

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

?

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

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

objects = collections2;

?

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

Object o = objects.get(0);

?

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

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

?

?

//可以add null

objects.add(null);

?

總結來說:?

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

四. 總結?

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/540312.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/540312.shtml
英文地址,請注明出處:http://en.pswp.cn/news/540312.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ASP.NET Session 詳解

[ASP.NET] Session 詳解 開發者在線 Builder.com.cn 更新時間:2008-03-23作者&#xff1a;黑暗凝聚力量&#xff0c;墮落方能自由 來源:CSDN 本文關鍵詞&#xff1a; Web開發 ASP session 詳解 本文僅代表作者個人觀點&#xff0c;正確與否請讀者自行研究&#xff01;閱讀本文…

[轉載] java給對象中的包裝類設置默認值

參考鏈接&#xff1a; Java中的對象類Object 處理方法如下 主要適用于&#xff0c;對象中使用了包裝類&#xff0c;但是不能給null需要有默認值的情況 /** * 處理對象中包裝類&#xff0c;因為快捷簽沒有用包裝類 * * param object 對象 */ public static void handlePara…

hadoop namenode管理元數據機制

一、簡要namenode管理元數據機制&#xff1a; 二、詳細namenode管理元數據機制&#xff1a; 三、secondary namenode 合并edits和fsimage&#xff1a; 四、namenode存儲元數據細節&#xff1a; 五、checkpoint觸發點&#xff1a; 本文轉自lzf0530377451CTO博客&#xff0c;原文…

[轉載] 多線程詳解java.util.concurrent

參考鏈接&#xff1a; java.lang.Object的靈活性 一、多線程 1、操作系統有兩個容易混淆的概念&#xff0c;進程和線程。 進程&#xff1a;一個計算機程序的運行實例&#xff0c;包含了需要執行的指令&#xff1b;有自己的獨立地址空間&#xff0c;包含程序內容和數據&#…

BABOK - 企業分析(Enterprise Analysis)概要

描述 企業分析描述我們如何捕捉、提煉并明晰業務需要&#xff0c;并定義一個可能實現這些業務需要的一個方案范圍&#xff0c;它包括問題定義和分析&#xff0c;業務案例開發&#xff0c;可行性研究和方案范圍定義 目的 明確業務戰略需要和目標&#xff0c;并建議方案范圍 任務…

6、EIGRP配置實驗之負載均衡

1、實驗拓撲 2、負載均衡原理 等價負載均衡&#xff1a;默認情況下EIGRP只支持等價負載均衡&#xff0c;默認支持4條線路的等價負載均衡&#xff0c;可以通過show ip protocols 查看&#xff0c;最大可以支持16條線路的等價負載均衡&#xff0c;可以在EIGRP路由進程下通過maxim…

[轉載] 詳解Java中靜態方法

參考鏈接&#xff1a; Java中的靜態類 定義&#xff1a; 在類中使用static修飾的靜態方法會隨著類的定義而被分配和裝載入內存中&#xff1b;而非靜態方法屬于對象的具體實例&#xff0c;只有在類的對象創建時在對象的內存中才有這個方法的代碼段。 注意&#xff1a; 非靜態…

[轉載] 向集合中添加自定義類型--建議在自定義類型的時候要重寫equals方法

參考鏈接&#xff1a; Java重寫equals方法 package com.bjpowernode.t01list; import java.util.ArrayList; /* * 向集合中添加自定義類型 */public class TestList04 { public static void main(String[] args) { ArrayList list new ArrayList(); Student s1 new Stude…

[轉載] java重寫toString()方法

參考鏈接&#xff1a; 在Java中重寫toString() 前言&#xff1a; 在你興高采烈的寫完一個類&#xff0c;創建測試類時&#xff0c;創建對象&#xff0c;傳入參數&#xff0c;調用對象&#xff0c;以為會得到參數值&#xff0c;但突然發現輸出的是“ 類名什么東東&#xff1f;&…

haproxy+keepalived實現負載均衡及高可用

HAProxy是一個使用C語言編寫的自由及開放源代碼軟件&#xff0c;其提供高性能性、負載均衡&#xff0c;以及基于TCP和HTTP的應用程序代理。相較與 Nginx&#xff0c;HAProxy 更專注與反向代理&#xff0c;因此它可以支持更多的選項&#xff0c;更精細的控制&#xff0c;更多的健…

[轉載] Java中變量與常量

參考鏈接&#xff1a; Java中的實例變量隱藏 1、變量的定義&#xff1a;定義變量就是要告訴編譯器這個變量的數據類型&#xff0c;這樣編譯器才知道需要分配多少空間給它&#xff0c;以及它能存放什么樣的數據。在程序運行過程中空間的值是變化的&#xff0c;這個內存空間就成…

Linux-實用快捷鍵操作

博文說明【前言】&#xff1a; 本文將通過個人口吻介紹Linux下一些常用的實用快捷鍵&#xff0c;在目前時間點【2017年6月14號】下&#xff0c;所掌握的技術水平有限&#xff0c;可能會存在不少知識理解不夠深入或全面&#xff0c;望大家指出問題共同交流&#xff0c;在后續工作…

iOS技術博客:App備案指南

&#x1f4dd; 摘要 本文介紹了移動應用程序&#xff08;App&#xff09;備案的重要性和流程。備案是規范App開發和運營的必要手段&#xff0c;有助于保護用戶權益、維護網絡安全和社會秩序。為了幫助開發者更好地了解備案流程&#xff0c;本文提供了一份最新、最全、最詳的備…

[轉載] Java中靜態成員變量,靜態代碼塊,靜態內部類何時被初始化?

參考鏈接&#xff1a; Java中的初始化程序塊Initializer Block 關于這個問題&#xff0c;本文不扯理論&#xff0c;直接上代碼&#xff0c;通過結果來驗證結論&#xff0c;廢話少說&#xff0c;測試代碼如下&#xff1a; public class StaticTest { public static StaticMem…

mikrotik dhcp server

操作路徑: /ip dhcp-server 關聯操作: /ip pool屬性 述 dhcp server interface (名稱) – 選擇 DHCP 服務的網絡接口 dhcp address space (IP 地址/掩碼; 默認: 192.168.0.0/24) – DHCP 服務器將出租給客戶端的網絡地 址段 gateway (IP 地址; 默認: 0.0.0.0) – 分配給客戶端的…

[轉載] Java static關鍵字與static{}語句塊

參考鏈接&#xff1a; Java中的靜態塊static block 目錄直通車 一、 類的加載特性與時機 1、 類加載的特性 2、 類加載的時機 二、 static的三個常用 1、 修飾成員變量 2、 修飾成員方法 3、 靜態塊&#xff08;static{}&#xff09; 一、 類的加載特性與時機 …

Perl文件讀寫操作

本文轉自 tiger506 51CTO博客&#xff0c;原文鏈接&#xff1a;http://blog.51cto.com/tiger506/830771&#xff0c;如需轉載請自行聯系原作者

[轉載] Java 語言中的實例初始化塊 ( IIB) 詳解

參考鏈接&#xff1a; Java中的實例初始化塊(IIB) 在 Java 語言中的類初始化塊 文章中我們簡單的介紹了下 Java 中的實例初始化塊 ( IIB )。不過我覺得介紹的有點簡單了&#xff0c;于是&#xff0c;再寫一篇文章詳細介紹下吧。 Java 語言中&#xff0c;存在三種操作&#x…

不用正則表達式,用javascript從零寫一個模板引擎(一)

前言 模板引擎的作用就是將模板渲染成html&#xff0c;html render(template,data)&#xff0c;常見的js模板引擎有Pug,Nunjucks,Mustache等。網上一些制作模板引擎的文章大部分是用正則表達式做一些hack工作&#xff0c;看完能收獲的東西很少。本文將使用編譯原理那套理論來打…

[轉載] Java靜態綁定與動態綁定

參考鏈接&#xff1a; Java中的靜態綁定與動態綁定 程序綁定的概念&#xff1a; 綁定指的是一個方法的調用與方法所在的類(方法主體)關聯起來。對java來說&#xff0c;綁定分為靜態綁定和動態綁定&#xff1b;或者叫做前期綁定和后期綁定. 靜態綁定&#xff1a; 在程序執行前方…