深入淺出 Java 泛型之(一):前生今世

LoulanPlan

本文出自伯特的《LoulanPlan》,轉載務必注明作者及出處。

對于 Java 開發者而言,泛型是必須掌握的知識點。泛型本身并不復雜,但由于涉及的概念、用法較多,所以打算通過系列文章去講解,旨在全面、通俗的介紹泛型及其使用。如果你是初學者,可以通過本文了解泛型,并滿足企業級開發的需求;如果你對泛型已有一定的了解,可以通過本文進行鞏固,加深對泛型的理解。

作為系列文章的第一篇,本文將帶你了解 Java 泛型的前生今世,看看泛型的誕生之于開發者的意義。

1. 泛型之前:通用數據類型

對于集合框架中的 List 及其實現類,想必大家都不陌生。同時,泛型誕生之后即被廣泛運用于 Java 集合框架。所以,我們就以 List 作為觀察對象,看看在泛型誕生之前,Oracel 的工程師們是如何進行設計的。

摘自 JDK 1.4 的 List.java 源碼:

public interface List extends Collection {
    //添加元素
    boolean add(Object o);
    //查詢元素
    Object get(int index);
}

可以看出 List 是通過 Object 類型管理的數據,如此設計的好處顯而易見:

具備通用性,因為所有的類都是 Object 的直接或間接子類,所以適用于任意類型的對象

同時,弊端也是不可忽視的。下面就通過使用 List 存、取數據來看看都有哪些問題:

//構造對象
List list = new ArrayList();
//存
list.add(1);
list.add("2");//①
//取
int num1 = (int)list.get(0);
int num2 = (int)list.get(1);//②

由于使用 Object,編譯器無法判斷存、取數據的實際類型,導致上述幾行代碼暴露出許多問題:

  1. 無法限制存儲數據類型,不夠健壯:在 ① 處可以添加 String 類型數據,顯然是臟數據;
  2. 取出時強轉代碼冗余,可讀性差:取出數據時必須顯示強轉為 int 類型;
  3. 由于 ① 處在編譯時無法檢查出錯誤,導致 ② 處的強轉在運行時引發 ClassCastException,安全性低;

問題還真不少!

2. 泛型萌芽:數據類型的包裝

上述問題究其根本,是無法限制數據類型引起的。也就是說,如果我們基于 List 包裝出相應類型的 XxxList,就可以解決問題。

舉個例子,包裝用于存儲 Integer 數據類型的 IntegerList

public class IntegerList {
    List list = new ArrayList();

    //限制外部只能添加整型數據
    public boolean add(Integer data) {
        return list.add(data);
    }

    //內部進行強轉,調用者可以直接賦值為整型
    public Integer get(int index) {
        return (Intrger)list.get(index);
    }
}

包裝內依然使用 List 管理數據,但我們對外暴露的接口限制了數據類型,規避了直接訪問 List 的接口可能引發的問題。

下面一起來看看如何使用包裝類:

//構造對象
IntegerList list = new IntegerList();
//存
list.add(1);
list.add("2");//①
//取
int num1 = list.get(0);

怎么樣,一個包裝類輕松解決問題:

  1. 在 ① 處試圖添加 String 類型數據,會在編譯期進行類型檢查時報錯,導致編譯失敗;
  2. 在取出數據時,無需重復強轉,直接賦值給 int 類型的數據;
  3. 因為限制了 add() 方法的參數類型,所以不用擔心在 get() 時內部強轉會引發異常。

簡直完美。同理,可以包裝出一系列 StringList, LongList,以及自定義數據的集合包裝類 PeopleList, DataList 等。

但人無完人,類亦無完類啊。包裝類雖解決了編碼上的數據類型問題,可在工程效率方面卻捉襟見肘:

  • 復用性低:每一個包裝類只適用于一種數據類型,無法復用核心邏輯;
  • 維護成本高:復用性低必然會增加后期維護的成本。

仍需努力!

3. 泛型登場:參數化類型

雖然包裝類存在缺陷,但其對于理解泛型思想是很有意義的。不知 Oracle 的工程師們,是否受此啟發設計出的泛型呢?

如果你試著多寫幾個數據類型的包裝類,就會發現各包裝類之間的區別和聯系:

  1. 區別:數據類型不同;
  2. 聯系:操作數據的方法相同,即核心算法邏輯是一致的。

既然如此,如果我們能夠弱化數據類型,使其不再受具體的業務場景限制,就可以做到專注于通用的算法邏輯,從而提升復用性。

那么,如何弱化數據類型呢?有人說了,使用 Object 就很弱化啊。咳,麻煩你從頭開始看。。。

JDK 5(即 JDK 1.4 之后的 1.5) 引入了 泛型(Generic Type) 的概念,其通過“參數化類型”實現數據類型的弱化,使得程序內部不需要關心具體的數據類型,而是讓業務在調用時作為參數傳入。泛型將傳入的數據類型傳遞給編譯器,這樣編譯器就可以在編譯期間進行類型檢查,確保程序的安全性,并且可以插入相應的強轉以避免開發人員顯示強轉。

上面這段話值得多讀幾遍,尤其是“參數化類型”可以說是泛型的核心所在。如果還有點蒙沒關系,繼續往下看。

Java 中方法的聲明大家都不陌生,如果某個方法需要對整數進行加法運算,我們可以在聲明方法時添加整數類型的參數,外部調用時必須傳入相應的整數數據。這里,將數據抽象為參數的過程,可以理解為“參數化實參”。

那么,“參數化類型”可以理解為是“參數化數據”的進一步抽象:將數據類型抽象為參數,即類型形參。如此一來,數據類型可以像形參一樣,在調用時動態指定。如此,就達到了使用通用邏輯動態處理不同數據類型的目的。

下面,我們通過 JDK 源碼中有關泛型的運用來鞏固這一概念。

4. 泛型的簡單運用

泛型誕生后,即對 Java 集合框架進行了大刀闊斧的修改,引入了泛型。下面仍然以 List 作為觀察對象,看看泛型帶來了哪些改變。

//摘自 JDK 5 版本的 List 源碼
public interface List<E> extends Collection<E> {
    //添加元素
    boolean add(E e);
    //指定下標查詢元素
    E get(int index);
    //指定下標移除元素
    E remove(int index);
}

可以看出,List<E> 通過在類 List 后追加 <> 標識其為泛型類,包含的元素 E 即“類型形參“,以支持開發者在使用時指定實際類型。下面看看在代碼中如何使用泛型 List

//構造對象
List<Integer> list = new ArrayList();
//存
list.add(1);
list.add("2");//①
//取
int num1 = list.get(0);
int num2 = list.get(1);

首先,我們構造了 List<Integer> 類型的對象,所以在運行時 List<E> 中的形參會被當做 Integer 去出處理,我們可以想象出一個虛擬的 List 類:

public interface List extends Collection<E> {
    boolean add(Integer e);
    Integer get(int index);
    Integer remove(int index);
}

接下來,和文章開頭一樣,我們對集合進行了相關操作,可以看出使用泛型解決了我們之前遇到的所有問題:

  1. ① 處的代碼在編譯期間會出錯:由于聲明的是 Integer 類型的 List,顯然無法接收 String 類型的數據。
  2. 從虛擬 List 可以知道,取出元素時不需要顯示強轉,自然也不會在運行時拋出異常。

通過對泛型 List 的簡單運用,可以看出引入泛型后集合不失普適性,依然可以針對各種類型對象進行操作。同時,泛型為集合框架增加了編譯時類型安全性,并避免了在使用過程中的強轉操作。

5. 總結

有關泛型的前生今世就介紹到這兒了。至此,我們通過相關示例一步步引出了泛型,了解了泛型誕生前后在一些編碼場景下的差異。最后還通過實例簡單使用了泛型,但泛型的運用遠不止如此...

下一篇將進一步介紹泛型的各種運用場景,掌握泛型的用武之地。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容