本文出自伯特的《 LoulanPlan》,轉載務必註明做者及出處。
對於 Java 開發者而言,泛型是必須掌握的知識點。泛型自己並不複雜,但因爲涉及的概念、用法較多,因此打算經過系列文章去講解,旨在全面、通俗的介紹泛型及其使用。若是你是初學者,能夠經過本文了解泛型,並知足企業級開發的需求;若是你對泛型已有必定的瞭解,能夠經過本文進行鞏固,加深對泛型的理解。java
做爲系列文章的第一篇,本文將帶你瞭解 Java 泛型的前生今世,看看泛型的誕生之於開發者的意義。git
對於集合框架中的 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
,編譯器沒法判斷存、取數據的實際類型,致使上述幾行代碼暴露出許多問題:編碼
String
類型數據,顯然是髒數據;ClassCastException
,安全性低;問題還真很多!設計
上述問題究其根本,是沒法限制數據類型引發的。也就是說,若是咱們基於 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);
怎麼樣,一個包裝類輕鬆解決問題:
String
類型數據,會在編譯期進行類型檢查時報錯,致使編譯失敗;add()
方法的參數類型,因此不用擔憂在 get()
時內部強轉會引起異常。簡直完美。同理,能夠包裝出一系列 StringList, LongList,以及自定義數據的集合包裝類 PeopleList, DataList 等。
但人無完人,類亦無完類啊。包裝類雖解決了編碼上的數據類型問題,可在工程效率方面卻捉襟見肘:
仍需努力!
雖然包裝類存在缺陷,但其對於理解泛型思想是頗有意義的。不知 Oracle 的工程師們,是否受此啓發設計出的泛型呢?
若是你試着多寫幾個數據類型的包裝類,就會發現各包裝類之間的區別和聯繫:
既然如此,若是咱們可以弱化數據類型,使其再也不受具體的業務場景限制,就能夠作到專一於通用的算法邏輯,從而提高複用性。
那麼,如何弱化數據類型呢?有人說了,使用 Object 就很弱化啊。咳,麻煩你從頭開始看。。。
JDK 5(即 JDK 1.4 以後的 1.5) 引入了 泛型(Generic Type)
的概念,其經過「參數化類型」實現數據類型的弱化,使得程序內部不須要關心具體的數據類型,而是讓業務在調用時做爲參數傳入。泛型將傳入的數據類型傳遞給編譯器,這樣編譯器就能夠在編譯期間進行類型檢查,確保程序的安全性,而且能夠插入相應的強轉以免開發人員顯示強轉。
上面這段話值得多讀幾遍,尤爲是「參數化類型」能夠說是泛型的核心所在。若是還有點蒙不要緊,繼續往下看。
Java 中方法的聲明你們都不陌生,若是某個方法須要對整數進行加法運算,咱們能夠在聲明方法時添加整數類型的參數,外部調用時必須傳入相應的整數數據。這裏,將數據抽象爲參數的過程,能夠理解爲「參數化實參」。
那麼,「參數化類型」能夠理解爲是「參數化數據」的進一步抽象:將數據類型抽象爲參數,即類型形參。如此一來,數據類型能夠像形參同樣,在調用時動態指定。如此,就達到了使用通用邏輯動態處理不一樣數據類型的目的。
下面,咱們經過 JDK 源碼中有關泛型的運用來鞏固這一律念。
泛型誕生後,即對 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); }
接下來,和文章開頭同樣,咱們對集合進行了相關操做,能夠看出使用泛型解決了咱們以前遇到的全部問題:
Integer
類型的 List
,顯然沒法接收 String
類型的數據。List
能夠知道,取出元素時不須要顯示強轉,天然也不會在運行時拋出異常。經過對泛型 List
的簡單運用,能夠看出引入泛型後集合不失普適性,依然能夠針對各類類型對象進行操做。同時,泛型爲集合框架增長了編譯時類型安全性,並避免了在使用過程當中的強轉操做。
有關泛型的前生今世就介紹到這兒了。至此,咱們經過相關示例一步步引出了泛型,瞭解了泛型誕生先後在一些編碼場景下的差別。最後還經過實例簡單使用了泛型,但泛型的運用遠不止如此...
下一篇將進一步介紹泛型的各類運用場景,掌握泛型的用武之地。