搜索
Table_bottom

标签云
Table_bottom

分类
Table_bottom

声明
文章若未特別註明,皆採用 知识共享许可协议 請自覺遵守
Table_bottom

鏈。。。
Table_bottom

存档
Table_bottom

匆匆过客
88547
Table_bottom

功能
Table_bottom

Java的泛型——坑、優秀與缺陷

人云E云 posted @ 2017年12月31日 19:22 in 信息技術 with tags java Coding Programming Language , 2060 阅读

最近一直在寫一個自己的Android程序(https://github.com/renyuneyun/Easer),所以Java用得比較多。又由於我懶,所以總喜歡讓編譯器做更多,於是想到用泛型來解決之前存在的Object滿地飛又滿地強制類型轉換的情況。然而這時候卻發現,泛型只能解決其中一部分問題,另一部分問題依然存在。

於是起意記錄一下自己知道的、用過的以及碰到的東西,以期有人能給出更加的解決方案(或是乾脆直接指出我錯了最好。。。這樣解決起來最簡單)。

 

一般而言,Java的泛型可以讓程序員寫出一些“形式相同,但具體參數類型不同”的代碼。從字面上說,這一機制在許多語言中都有(如C++的模板),但由於各個語言的實現方式和語義取捨不同,導致具體支持的功能千差萬別。

本文主要集中於Java泛型機制中的坑爹之處。爲了介紹坑爹之處,於是也就需要涉及該機制的理解,同時也會簡單涉及其實現部分。當然,其中一些有意義的地方也會順帶提及(在做對比時)。

引子

由於我個人對語言的學習順序,C++的模板機制是我首先接觸的(不考慮C的宏,因爲其只是簡單的文本替換),Java的泛型是在其後學習的。最初學習時,我以爲Java的泛型和C++的模板(不考慮變量做模板參數,因爲當時沒有學過)幾乎就是一樣的,無非是字面上的語法有點區別。但隨着看了更多的一些Java代碼,發現Java的泛型還支持對類型參數進行一定限制/細化(extendssuper關鍵字);隨着寫了更多Java代碼,發現Java的泛型之下類型參數不能完全被當作類型名來用(例如不能new一個新對象,不能寫在instanceof關鍵字之後)。

最後,最終導致寫這篇文章的契機,則是爲了讓編譯器做更多檢查,我嘗試在我的代碼中需要寫出可以接受類型參數的類型參數(嵌套的泛型)——這一機制完全無法被Java支持。在翻閱一定資料之後,發現Java的泛型和我原來的理解有很大不同。

Java泛型的表象

在Java中,泛型的出現是爲了解決Object滿地跑的現象:如果沒有泛型,List之類的容器(即自動增長的數據類型)內將會是滿地的Object——add的參數類型會是Objectget的返回值類型會是Object……這就導致了類型安全完全無法保障:程序員可以任意地向一個List中傳遞(添加)任意類型的數據,從而導致使用方(get)出現問題。更不要提滿地的強制類型轉換了。

雖然泛型的出現解決了這一問題,使得List等容器可以使用泛型來書寫(聲明和定義),程序員只須在使用時指明要使用的類型即可。或許和該主要需求有關,Java對泛型的支持方式導致一般情況下泛型的作用變成了“存儲”相關,而非“功能”相關。這使得它和C++的模板功能相比起來在“泛”這個程度上差了很多。

表象上來說,Java泛型和直覺中其能幹的事情所差的部分有這些:

  1. Java泛型的類型參數無法被new,無法被用於instanceof檢查(這兩者均有workaround)
  2. Java泛型的類型參數無法直接.class
  3. Java泛型無法(以符合直覺的方式)寫出諸如addsum之類的數學函數
  4. Java泛型的類型參數無法是一個泛型(即無法達成嵌套的泛型)

(如果你無法想像這幾項的應用場景,那麼有可能你並不是本文的受衆。)

前兩項表面上看令人很詫異,畢竟從直覺上這些都是理所當然的:我(程序員)明知那個T是一個“類型名”,爲什麼不可以將其用在這些需要類型名的地方;第三點其實在某種程度上已經反映了Java泛型的一些限制及機制;而最後一項雖然許多人或許用不到,但也反映了Java泛型機制的一個限制。

實際上,這幾項均是Java泛型的機制本身決定的限制,並非是什麼“額外的問題”——其主要原因在於Type Erasure,但也有部分(主要是最後一項,也有第三項)不完全歸咎於Type Erasure。

Java泛型的機制:優點與限制

“優點”主要是指和C++相比,Java泛型所能額外做的一些事情。但整體考量下來,其限制更大,而且這些優點在某種意義上有些不倫不類(甚至在部分情況下可以說是冗餘)。

在閱讀下文之前,請向自己強調一下:Java是編譯型語言(雖然是編譯到字節碼)。

Bounded Type Parameter

Java的泛型支持在聲明類型參數時同時指定其“可能”的上界或下界(通過extendssuper關鍵字來指定)。那些指明了上界或下界的類型參數被稱爲Bounded Type Parameter(受限類型參數),而未指明的則被稱爲Unbounded Type Parameter(未受限類型參數)。由於Java中所有類均默認繼承自Object類,所以未受限類型參數也可以被視爲上界爲Object受限類型參數。

abstract class MyClass {
  public void print();
}

<T extends MyClass> void func(T a) {
  a.print();
}

該機制的兩面(上界和下界)分別具有不同用處,雖然本質上有相似性但表面上(使用上)有很大不同:指明上界會限制該段程序只能對其進行讀取,而無法寫入;指明下界會限制該段程序只能對其進行寫入,而無法讀取。個人覺得Kotlin的文檔對此講得較爲清楚。

在C++處,語法上並不支持該特性。但由於C++的模板機制和Java的泛型有很大不同,所以對此的缺失在語言功能的角度上是可以接受的(但IDE的自動補全或提示功能受到的影響無法解決)。

Type Erasure

Java處理泛型的機制中一個最重要的部分叫做Type Erasure。該機制保證Java泛型僅存在於編譯期而非運行時,但同時它也是Java泛型功能限制的罪魁禍首(但似乎Java對此很滿意?)。

Type Erasure主要做三件事

  1. 將類型參數直接替換爲其約束邊界(即extends後的那個類型;沒有extends可以視爲extends Object
  2. 在需要的地方自動加入強制類型轉換(以保持類型安全)
  3. 生成橋接代碼,以便維持多態的正確執行

看起來似乎做了很多事,但實際上,前兩點直接說明了Java泛型的本質:泛型沒有增加新東西,所有的只是多態(繼承上的多態)和自動書寫的強制類型轉換;最後一點則是爲了正確進行對重寫函數的調用(橋接到重載函數上)。

所以,存在於編譯期的多態機制,在被編譯成字節碼以後就已經消失,而被替換成了我們所熟悉的強制類型轉換。這也就解釋了爲什麼我們無法對類型參數進行newinstanceof檢查或是調用.class:因爲它們(在最壞的情況下)被替換爲了Object而非我們所要(用)的那個類型,在Object上的調用並不是我們需要的。

泛型數學運算

前文提過,泛型狀態下我們無法進行數學計算,這主要歸咎於Type Erasure,但又不全歸咎於它。本節更詳細解釋這一問題。

在Java中,只有primitive type可以使用+-等算數運算符(不考慮String+代表的“連接”),但泛型類型參數只能爲類,所以天生無法調用這些算數運算符。即使我們考慮auto(un)boxing,由於Type Erasure的存在,(在泛型函數中)我們所使用的變量的類型處於未知狀態,所以該機制也無從發揮(即編譯期需要考慮:Number類(甚至是Object類)的unboxing是什麼?)。

但從另一個角度,我們會發現即使Java的Generics設定成這樣,Type Erasure存在,在對語言的其他設定進行一定變動之後泛型就可以支持數學計算了:只要Java支持運算符重載。然而看起來Java似乎並沒有打算支持運算符重載。

嵌套的泛型

對這一機制的需求源於我代碼的一種設計,該設計可以簡化爲這樣:

interface Data {
}

interface CatData extends Data {
}

interface DogData extends Data {
}

class Box<T extends Data> {
	T data;
	T getData() {
		return data;
	}
}

class CatBox<T extends CatData> extends Box<T> {
}

class DogBox<T extends DogData> extends Box<T> {
}

class PuppyData extends DogData {
}

class PuppyDogBox extends DogBox<PuppyData> {
}

class Asset<T extends Box> {
	<D extends Data> T findBox(D data) {
		// Do something
	}
}

class DogAsset extends Asset<DogBox> {
}

由於這段代碼中的DogBox實際上也是泛型,我們期望Asset(及其子類)的findBox函數的返回值攜帶類型信息,即Asset的定義可以形如這樣:

class Asset<T extends Box> {
	<D extends Data> T<D> findBox(D data) {
		// Do something
	}
}

這樣,當調用DogAsset.findBox(new PuppyData())時,其返回值類型爲DogBox<PuppyData>(即對應於PuppyDogBox),而非僅僅是DogBox

這就是本文所謂的“嵌套的泛型”。該機制在Java中並不被支持,所以在調用Asset.findBox()函數時返回值類型需要再額外進行一下限定(忽略編譯器警告)。

而就我所知,C++的模板支持這種做法(template template parameter);在查閱資料時,發現Scala也支持這種做法(higher kinded type)。

總結

Java泛型的實現約等於自動書寫強制類型轉換,並沒有給運行時增加額外特性。雖然其中有一些有意義的設計(如Bounded Type Parameter),但整體而言其缺點似乎更爲嚴重。對我目前的使用來說,其所最主要欠缺的是嵌套的泛型,類似於C++的template template parameter或Scala的higher kinded type。

atang 说:
2018年2月09日 15:15

Easer 和 Tasker 是否有类似的地方呢。

Avatar_small
人云E云 说:
2018年2月18日 22:45

應該很多方面很像吧,但我沒用過tasker,所以不確定細節或是“通用性”上有多少類似。

其實我最開始並不知道Tasker的存在,但我以前用過一個叫ProfileSwitcher的軟件,它的功能靈活性就比較差。
寫Easer的一個目的是爲了允許複雜邏輯的存在,或者說允許用戶來進行一定程度上的編程(但不是通過寫代碼的形式)。(當然另一個原因就是沒見過開源的這類軟件……)

NGO full form 说:
2022年8月01日 09:52

NGO is Non Governmental Organization and it is an independent organization which is a nonprofit group. These NGOs also referred to as civil societies and organized at national, NGO full form Community or international level with an aim to serve for social or political growth by meeting environmental or humanitarian causes. NGO do play a vital role in the development of nations with their international aid and other philanthropy. These organizations are fully not profit based and can run with a budget of millions which can also grow to billions every financial year.

Earning App Registra 说:
2022年11月09日 08:30

Technology allows online users to develop apps and websites for Companies or public use creatively. The internet registers thousands of applications claiming to help people create or earn money. Earning App Registration Some are pure scams, while others are legit and help people build wealth from home. Online users are advised not to venture into anything without checking its background and legitimacy.

cmbihar.in 说:
2023年5月03日 17:29

Cmbihar is a initiative of professional writers who have come together for dedicated news coverage of latest happenings around the country. cmbihar.in Our team comprises of professional writers and citizen journalists with diverse range of interest in Journalism who are passionate about publishing the Education Updates with transparency in general public interest.

facture apple.com/bi 说:
2023年7月14日 16:17

Apple bill prépare les factures et envoie une page Web pour les articles achetés. C’est à cause du piratage de leurs identifiants Apple ou des bordereaux d’informations de carte de crédit, ainsi que de l’achat d’un enfant innocent ou d’un membre de la famille à leur insu. facture apple.com/bill Les factures ou les factures des articles achetés tels qu’un téléphone, une contribution d’application, un stockage iCould, etc., seront envoyées via apple bill par Apple Company.

CBSE Sample Questio 说:
2023年8月24日 07:08

The lesson is Understood and the Questions are related to knowledge, Awareness, utility, skill, and eternal life. Hence Students have to follow the way to learn, not only to recollect Self-writing, and Group Discussion, Central Board of Secondary Education has stated that there will be two types of Mathematics test for Class X students from 2024. The current test Method will be Conducted CBSE Sample Question Paper 2024 as Mathes-Standard, Mathematics-Basic Level. The CBSE has made it clear that there is no Change in the Current Academic year.The CBSE has revealed that the fear is to escape those fears before the Children are looking for a difficult subject. The 2024 Board Examination revealed that this type of Experiment would be Implemented.

pavzi.com 说:
2024年1月23日 07:54

Pavzi.com is a startup by passionate webmasters and bloggers who have a passion for providing engaging content that is accurate, interesting, and worthy to read. pavzi.com We are more like a web community where you can find different information, resources, and topics on day-to-day incidents or news. We provide you with the finest web content on every topic possible with the help of the editorial and content team.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter