本文出自伯特的《LoulanPlan》,轉載務必注明作者及出處牲距。
對于 Java 開發(fā)者而言,泛型是必須掌握的知識點钥庇。泛型本身并不復雜牍鞠,但由于涉及的概念、用法較多上沐,所以打算通過系列文章去講解皮服,旨在全面、通俗的介紹泛型及其使用。如果你是初學者龄广,可以通過本文了解泛型硫眯,并滿足企業(yè)級開發(fā)的需求;如果你對泛型已有一定的了解择同,可以通過本文進行鞏固两入,加深對泛型的理解。
作為系列文章的第一篇敲才,本文將帶你了解 Java 泛型的前生今世裹纳,看看泛型的誕生之于開發(fā)者的意義。
1. 泛型之前:通用數(shù)據(jù)類型
對于集合框架中的 List
及其實現(xiàn)類紧武,想必大家都不陌生剃氧。同時,泛型誕生之后即被廣泛運用于 Java 集合框架阻星。所以朋鞍,我們就以 List
作為觀察對象,看看在泛型誕生之前妥箕,Oracel 的工程師們是如何進行設計的滥酥。
摘自 JDK 1.4 的 List.java
源碼:
public interface List extends Collection {
//添加元素
boolean add(Object o);
//查詢元素
Object get(int index);
}
可以看出 List
是通過 Object
類型管理的數(shù)據(jù),如此設計的好處顯而易見:
具備通用性畦幢,因為所有的類都是 Object 的直接或間接子類坎吻,所以適用于任意類型的對象。
同時宇葱,弊端也是不可忽視的瘦真。下面就通過使用 List
存、取數(shù)據(jù)來看看都有哪些問題:
//構造對象
List list = new ArrayList();
//存
list.add(1);
list.add("2");//①
//取
int num1 = (int)list.get(0);
int num2 = (int)list.get(1);//②
由于使用 Object
贝搁,編譯器無法判斷存吗氏、取數(shù)據(jù)的實際類型,導致上述幾行代碼暴露出許多問題:
-
無法限制存儲數(shù)據(jù)類型雷逆,不夠健壯:在 ① 處可以添加
String
類型數(shù)據(jù)弦讽,顯然是臟數(shù)據(jù); - 取出時強轉代碼冗余膀哲,可讀性差:取出數(shù)據(jù)時必須顯示強轉為 int 類型往产;
- 由于 ① 處在編譯時無法檢查出錯誤,導致 ② 處的強轉在運行時引發(fā)
ClassCastException
某宪,安全性低仿村;
問題還真不少!
2. 泛型萌芽:數(shù)據(jù)類型的包裝
上述問題究其根本兴喂,是無法限制數(shù)據(jù)類型引起的蔼囊。也就是說焚志,如果我們基于 List
包裝出相應類型的 XxxList
,就可以解決問題畏鼓。
舉個例子酱酬,包裝用于存儲 Integer
數(shù)據(jù)類型的 IntegerList
:
public class IntegerList {
List list = new ArrayList();
//限制外部只能添加整型數(shù)據(jù)
public boolean add(Integer data) {
return list.add(data);
}
//內部進行強轉,調用者可以直接賦值為整型
public Integer get(int index) {
return (Intrger)list.get(index);
}
}
包裝內依然使用 List
管理數(shù)據(jù)云矫,但我們對外暴露的接口限制了數(shù)據(jù)類型膳沽,規(guī)避了直接訪問 List
的接口可能引發(fā)的問題。
下面一起來看看如何使用包裝類:
//構造對象
IntegerList list = new IntegerList();
//存
list.add(1);
list.add("2");//①
//取
int num1 = list.get(0);
怎么樣让禀,一個包裝類輕松解決問題:
- 在 ① 處試圖添加
String
類型數(shù)據(jù)挑社,會在編譯期進行類型檢查時報錯,導致編譯失斞沧帷痛阻; - 在取出數(shù)據(jù)時,無需重復強轉腮敌,直接賦值給 int 類型的數(shù)據(jù)录平;
- 因為限制了
add()
方法的參數(shù)類型,所以不用擔心在get()
時內部強轉會引發(fā)異常缀皱。
簡直完美。同理动猬,可以包裝出一系列 StringList, LongList啤斗,以及自定義數(shù)據(jù)的集合包裝類 PeopleList, DataList 等。
但人無完人赁咙,類亦無完類啊钮莲。包裝類雖解決了編碼上的數(shù)據(jù)類型問題,可在工程效率方面卻捉襟見肘:
- 復用性低:每一個包裝類只適用于一種數(shù)據(jù)類型彼水,無法復用核心邏輯崔拥;
- 維護成本高:復用性低必然會增加后期維護的成本。
仍需努力凤覆!
3. 泛型登場:參數(shù)化類型
雖然包裝類存在缺陷链瓦,但其對于理解泛型思想是很有意義的。不知 Oracle 的工程師們盯桦,是否受此啟發(fā)設計出的泛型呢慈俯?
如果你試著多寫幾個數(shù)據(jù)類型的包裝類,就會發(fā)現(xiàn)各包裝類之間的區(qū)別和聯(lián)系:
- 區(qū)別:數(shù)據(jù)類型不同拥峦;
- 聯(lián)系:操作數(shù)據(jù)的方法相同贴膘,即核心算法邏輯是一致的。
既然如此略号,如果我們能夠弱化數(shù)據(jù)類型刑峡,使其不再受具體的業(yè)務場景限制洋闽,就可以做到專注于通用的算法邏輯,從而提升復用性突梦。
那么诫舅,如何弱化數(shù)據(jù)類型呢?有人說了阳似,使用 Object 就很弱化啊骚勘。咳撮奏,麻煩你從頭開始看俏讹。。畜吊。
JDK 5(即 JDK 1.4 之后的 1.5) 引入了 泛型(Generic Type)
的概念泽疆,其通過“參數(shù)化類型”實現(xiàn)數(shù)據(jù)類型的弱化,使得程序內部不需要關心具體的數(shù)據(jù)類型玲献,而是讓業(yè)務在調用時作為參數(shù)傳入殉疼。泛型將傳入的數(shù)據(jù)類型傳遞給編譯器,這樣編譯器就可以在編譯期間進行類型檢查捌年,確保程序的安全性瓢娜,并且可以插入相應的強轉以避免開發(fā)人員顯示強轉。
上面這段話值得多讀幾遍礼预,尤其是“參數(shù)化類型”可以說是泛型的核心所在眠砾。如果還有點蒙沒關系,繼續(xù)往下看托酸。
Java 中方法的聲明大家都不陌生褒颈,如果某個方法需要對整數(shù)進行加法運算,我們可以在聲明方法時添加整數(shù)類型的參數(shù)励堡,外部調用時必須傳入相應的整數(shù)數(shù)據(jù)谷丸。這里,將數(shù)據(jù)抽象為參數(shù)的過程应结,可以理解為“參數(shù)化實參”刨疼。
那么,“參數(shù)化類型”可以理解為是“參數(shù)化數(shù)據(jù)”的進一步抽象:將數(shù)據(jù)類型抽象為參數(shù)鹅龄,即類型形參币狠。如此一來,數(shù)據(jù)類型可以像形參一樣砾层,在調用時動態(tài)指定漩绵。如此,就達到了使用通用邏輯動態(tài)處理不同數(shù)據(jù)類型的目的肛炮。
下面止吐,我們通過 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
即“類型形參“二拐,以支持開發(fā)者在使用時指定實際類型服鹅。下面看看在代碼中如何使用泛型 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
類型的數(shù)據(jù)铅辞。 - 從虛擬
List
可以知道厌漂,取出元素時不需要顯示強轉,自然也不會在運行時拋出異常斟珊。
通過對泛型 List
的簡單運用桩卵,可以看出引入泛型后集合不失普適性,依然可以針對各種類型對象進行操作倍宾。同時,泛型為集合框架增加了編譯時類型安全性胜嗓,并避免了在使用過程中的強轉操作高职。
5. 總結
有關泛型的前生今世就介紹到這兒了。至此辞州,我們通過相關示例一步步引出了泛型怔锌,了解了泛型誕生前后在一些編碼場景下的差異。最后還通過實例簡單使用了泛型变过,但泛型的運用遠不止如此...
下一篇將進一步介紹泛型的各種運用場景埃元,掌握泛型的用武之地。