前面介紹各類容器之時,經過在容器名稱後面添加包裹數據類型的一對尖括號,表示該容器存放的是哪一種類型的元素。這樣一來總算把Java當中的各種括號都湊齊了,例如包裹一段代碼的花括號、指定數組元素下標的方括號、容納方法輸入參數的圓括號,還有最近跟在容器名稱以後的尖括號。但是爲何尖括號要加到容器後面呢?它還能不能用於其它場合?若想對尖括號的前因後果究根問底,就得從泛型的概念提及了。
不論是方法仍是類,都支持輸入指定類型的參數,其中方法的輸入參數在調用方法時填寫,而類的輸入參數可經過構造方法傳遞。在這兩種參數輸入的狀況中,參數類型是早就肯定好的,只有參數值纔會動態變化,那要是連參數類型都不肯定,得等到方法調用或者建立實例的時候才能肯定參數類型,這可如何是好?爲解決此種需求,各種編程語言紛紛祭出泛型的絕招,所謂「泛型」它的表面意思是空泛的類型,也就是不明確的類型,既然類型在方法定義或者類定義時仍不明確,只好留待要用的時候再指定了。
爲了更好地理解泛型的根源,接下來先看個簡單的例子。現在有兩個數字,一個是整數1,另外一個是帶小數點的1.0,光光從算術方面比較的話,1與1.0確定相等。可是到了Java語言這裏,使用包裝整型變量保存整數1,使用包裝浮點型變量保存小數1.0f,而後兩者經過equals方法進行校驗,判斷結果倒是不等的。此處對整數與小數開展比較的代碼以下所示:html
Integer oneInt = 1; Float oneFloat = 1.0f; boolean equalsSimple = oneInt.equals(oneFloat); System.out.println("equalsSimple="+equalsSimple);
運行以上的測試代碼,發現日誌輸出信息爲「equalsSimple=false」,該結果看似咄咄怪事,實際上是必然的,由於它們的變量類型都不同,致使編譯器認爲兩者的類型尚不吻合,遑論其它。若想進行包裝數值變量之間的相等判斷,就必須把有關變量轉換爲相同類型,再做指定精度的數值一致性檢驗。考慮到Integer和Float都繼承自Number類型,系出同源的還有Long、Double等類型,因而可將這些包裝變量通通轉爲Number類型,而後從Number變量獲取雙精度數值加以比較。據此編寫的方法代碼示例以下:java
// 經過Number基類比較兩個數值變量是否相等 public static boolean equalsNumber(Number n1, Number n2) { return n1.doubleValue() == n2.doubleValue(); }
從上面的equalsNumber方法可見,它的輸入參數爲Number型,同時涵蓋了Number及其派生出來的全部子類。對於這種狀況,Java容許泛化參數類型,即先聲明一個由Number擴展而來的類型T,再把T做爲輸入參數的變量類型。下面是具體的類型泛化代碼:程序員
// 經過泛型變量比較兩個數值變量是否相等。利用尖括號包裹泛型的派生操做 public static <T extends Number> boolean equalsGeneric(T t1, T t2) { return t1.doubleValue() == t2.doubleValue(); }
雖然equalsNumber與equalsGeneric的參數格式有所不一樣,但實際上兩個方法是等價的,它們支持的入參類型都屬於Number及其子類。編程
緊接着再來看個數組元素拼接成字符串的例子,編碼調試的過程當中,程序員經常想知道某個數組裏面究竟放了哪些元素,這時便須要將數組的全部元素都打印出來。然而數組變量自身不能自動轉成字符串,只能經過Arrays工具的toString方法輸出拼接好的字符串,假若由程序員本身編碼去拼接數組元素,那又該如何處理?由於普通的數據類型也同時支持數組形式,因此要想整個通用的字符串拼接方法,必須找到這些數據類型的共同基類,剛好Java也提供了這個基類名叫Object,那末把「Object[]」看成通用的數組類型真是再合適不過了。如此一來,數組各元素的字符串拼接代碼就變成了下面這般:數組
// 把對象數組裏的各個元素拼接成字符串 public static String objectsToString(Object[] array) { String result = ""; if (array!=null && array.length>0) { for (int i=0; i<array.length; i++) { if (i > 0) { result = result + " | "; } result = result + array[i].toString(); } } return result; }
接着讓外部運行一段測試代碼,檢查看看字符串拼接是否正常運行,測試代碼以下:編程語言
Double[] doubleArray = new Double[] { 1.1, 2D, 3.1415926, 11.11 }; System.out.println("objectsToString=" + objectsToString(doubleArray));
運行上述的測試代碼,觀察輸出的日誌發現拼接功能徹底正常:工具
objectsToString=1.1 | 2.0 | 3.1415926 | 11.11
Object做爲普通數據類型的基類,天然它也支持泛化的寫法,即先聲明一個由Object擴展而來的類型T,再把T做爲輸入參數的變量類型,因而類型泛化的代碼格式形如「<T extends Object>」。因爲Object是Java默認的原始基類,如同你們自定義新類時都沒寫「extends Object」那樣,類型泛化也沒必要顯式寫明「extends Object」,所以「<T extends Object>」完成能夠簡寫爲「<T>」。這樣採起泛化簡寫的字符串拼接泛型代碼以下所示:測試
// 把泛型數組裏的各個元素拼接成字符串。<T> 等同於 <T extends Object> //public static <T extends Object> String arraysToString(T[] array) { public static <T> String arraysToString(T[] array) { String result = ""; if (array!=null && array.length>0) { for (int i=0; i<array.length; i++) { if (i > 0) { result = result + " | "; } result = result + array[i].toString(); } } return result; }
如今給出了數組類型的泛型寫法,容器類型也能依樣畫葫蘆,對應於泛型數組的「T[]」,原先通用的清單數據就變成了類型「List<T>」。改寫以後的清單元素拼接代碼示例以下:編碼
// 把List清單裏的各個元素拼接成字符串,此處使用了泛型 public static <T> String listToString(List<T> list) { String result = ""; if (list!=null && list.size()>0) { for (int i=0; i<list.size(); i++) { if (i > 0) { result = result + " | "; } result = result + list.get(i).toString(); } } return result; }
對於包括清單在內的容器類型來講,還能在尖括號內部填上問號,一樣表示裏面的數據類型是不肯定的,就像下列代碼演示的那樣:調試
// 把List清單裏的各個元素拼接成字符串,此處使用了問號表示不肯定類型 public static String listToStringByQuestion(List<?> list) { String result = ""; if (list!=null && list.size()>0) { for (int i=0; i<list.size(); i++) { if (i > 0) { result = result + " | "; } result = result + list.get(i).toString(); } } return result; }
不過帶有問號的「<?>」寫法有很大的侷限性,它既不如泛型靈活,也不如Object通用。問號寫法僅僅適用於個別場合,並不推薦在通常方法中運用。單單拿問號跟泛型比較的話,主要有如下幾點區別:
一、問號只能用於給泛型類建立實例,自己不能建立實例。而泛型T既可用於泛型類建立實例,也可用於給自身建立實例,如「T t;」
二、問號只可用做輸入參數,不可用做輸出參數。而泛型T用於兩者皆可。
三、使用了問號的容器實例,只容許調用get方法,不容許調用add方法。而泛型容器不存在方法調用的限制。
更多Java技術文章參見《Java開發筆記(序)章節目錄》