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

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

對於 Java 開發者而言,泛型是必須掌握的知識點。泛型自己並不複雜,但因爲涉及的概念、用法較多,因此打算經過系列文章去講解,旨在全面、通俗的介紹泛型及其使用。若是你是初學者,能夠經過本文了解泛型,並知足企業級開發的需求;若是你對泛型已有必定的瞭解,能夠經過本文進行鞏固,加深對泛型的理解。java

做爲系列文章的第一篇,本文將帶你瞭解 Java 泛型的前生今世,看看泛型的誕生之於開發者的意義。git

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

對於集合框架中的 List 及其實現類,想必你們都不陌生。同時,泛型誕生以後即被普遍運用於 Java 集合框架。因此,咱們就以 List 做爲觀察對象,看看在泛型誕生以前,Oracel 的工程師們是如何進行設計的。github

摘自 JDK 1.4 的 List.java 源碼:算法

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

能夠看出 List 是經過 Object 類型管理的數據,如此設計的好處顯而易見:安全

具有通用性,由於全部的類都是 Object 的直接或間接子類,因此適用於任意類型的對象框架

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

//構造對象
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,就能夠解決問題。code

舉個例子,包裝用於存儲 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. 總結

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

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

相關文章
相關標籤/搜索