摘要:分析非泛型的Java系統存在的數據類型轉換安全問題,提出基于Java泛型的解決方案。通過對泛型的原理分析,探討Java泛型的實際應用。并在此基礎上,總結Java泛型的的特點。
關鍵詞:泛型;對象包裝;數據類型轉換;集合
中圖分類號:TP393文獻標識碼:A文章編號:1009-3044(2008)22-690-03
Research on Java Generics
WU De-peng
(Department of Computer Science Information Engineering,Guangxi Technological College of Machinery and Electricity, Nanning 530007,China)
Abstract: This paper analyses security problem of the data type converting in non-generics Java system and put up the solution which bases on generics. Through the generics theory analysis, study practical application of generics in Java. On this basis, sum up the feature of generics in Java.
Key words: generics; Object packaging; data type converting; collection
1 引言
Java語言的泛型是JDK1.5(Java Development Kit,Java開發工具包)版本之后引入的新特性。在基于Java的系統開發中,經常需要頻繁地對一些對象進行包裝和取出。包裝操作一般通過集合來操作,即將多個對象實例加入到集合對象中管理和傳遞,然后在適當的時機在將這些對象實例從集合中取出并還原成原來的數據類型。在JDK1.5以前的版本,在進行多個對象包裝時,集合類一般都規定所其所容納的對象為Object類型,即所有Java類的最高父類。從集合里取出對象時,再根據需要進行強制類型轉換,從而還原成包裝前的對象的數據類型。這樣的操作過程存在有潛在的類型轉換安全問題。假設系統開發人員從一個集合里取出對象的時候無法知道包裝前對象的數據類型,會導致無法完成轉換或者隱含有類型轉換錯誤。針對這個問題,JDK1.5引入了泛型的概念,它能在創建一個Java集合時顯式指定其所能容納的數據類型,并在取出時明確告訴系統開發人員取出的對象是什么數據類型,這樣避免了包裝和取出的數據類型不一致,從而有效地避免代碼中的潛在類型轉換安全錯誤。
2 Java泛型的原理
2.1 非泛型的數據類型轉換存在的問題
為了更深入理解Java泛型的概念,我們先來看一個沒有引入泛型的例子:
package test.generics;
import Java.util.ArrayList;
public class TestApp1 {
public static void main(String[] args) {
ArrayList list=new ArrayList();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Double(5.23));
for (int i=0;i<list.size();i++) {
Integer temp=(Integer) list.get(i);
System.out.println(temp.intValue());
}
}
}
本例中,建立了一個ArrayList的實例,并分別把整數1、2和浮點數5.23添加給它。然后循環遍歷該ArrayList,從中取出整型值并打印到控制臺上。這個程序編譯的時候并沒有錯誤,能被編譯通過,但是運行的時候JDK卻拋出了如下異常:
Exception in thread \"main\"
Java.lang.ClassCastException: Java.lang.Double
這是因為程序運行時,在循環里進行取出對象的時候,試圖將Double類型轉換成Integer類型,這顯然是不合理的。
作為一個習慣于使用語言提供的類型安全的系統開發人員,希望這樣的問題在編譯期間就能被發現,而不是潛伏到運行的時刻才拋出錯誤,這正是泛型產生的原因。
2.2 Java泛型的概念
泛型(Generic type 或者 generics)是對 Java 語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。類型參數是使用參數化類型時指定的類型的一個占位符,就像方法的形式參數是運行時傳遞的值的占位符一樣。
可以在集合框架(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String)的對象。
2.3 Java泛型的基本原理
假如在程序代碼中使用了泛型,即在創建一個對象集合的時刻顯式指定該集合類所能容納的對象數據類型,如下語句所示:
ArrayList<Integer> list=new ArrayList<Integer> ();
則Java編譯器在編譯時檢查所有和ArrayList集合類相關的操作代碼,這些操作代碼主要包括對集合類添加對象、修改對象等。如果類型和要求的不一致,即不屬于Integer類型,程序將無法被編譯通過。這樣確保往ArrayList中添加的對象是預定義的安全類型。
因為編譯時已經在包裝時進行了類型安全檢查,在進行取出對象的時候,程序已經知道了集合里所裝的對象的數據類型,所以拆包變得異常簡單而且準確,只需要將對象從集合里取出,即可恢復成包裝前的數據類型,無須再進行強制數據類型轉換。從泛型包裝和取出對象的原理,我們可以知道,泛型對類型轉換的檢查不再推遲到運行時刻,而是在編譯時就把這步工作完成。這一個重要的改變使得系統開發人員能在編譯階段就能夠發現系統潛在的危險,而不是等到運行的時候再發現。
3 Java泛型的應用
3.1 基本應用
從JDK1.5版本開始,引入了泛型的概念,使得系統開發人員能靈活地定義和使用和泛型相關的一些對象。其主要應用在Jdk自帶的集合框架類中。如ArrayList、HashMap、Hashtable等集合對象。下面舉一個例子來說明泛型在這些集合類中的基本應用。
package test.generics;
import Java.util.HashMap;
public class TestApp2 {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<String,String>();
map.put(\"key1\", \"value1\");
map.put(\"key2\", \"value2\");
for (int i = 0; i < map.size(); i++) {
String temp = map.get(i);
System.out.println(temp);}
}
}
本例中,首先創建一個HashMap的實例,即容納對象的集合。注意在創建這個HashMap的實例時,需要同時指明該集合所容納的數據類型為<String,String>。這一步十分關鍵,是編譯器進行類型安全檢查的基本依據。這里指定主鍵和值的數據類型均為String類型。然后分別把兩個配對值(\"key1\",\"value1\")和(\"key2\",\"value2\")加入HashMap對象中,完成了包裝的功能。
創建了HashMap<String,String>這樣一個對象實例,實際上就限定HashMap中能容納的主鍵key和值value的數據類型都是String。如果試圖將其它數據類型加入這個HashMap對象中,如使用下面的代碼:
map.put(“key3”,new Integer(1));
是不允許的,編譯時會拋出如下錯誤:
The method put(Stirng , String) in the type HashMap<String , String> is not applicable for the arguments(String,Integer)
即在編譯時,JDK對泛型類HashMap的包裝操作進行了數據類型安全檢查。在這段代碼中,它只能容納(String,String)類型的數據,試圖將(String,Integer)類型的數據加進這個HashMap對象是不適當的。
程序接著進行從集合里取出對象的操作。注意有泛型的取出對象操作要比沒有泛型的容易得多,只有簡單的取出,不再需要進行強制的類型轉換了。
3.2 擴展應用
基于Java泛型在集合中的基本應用,可以將其擴展到其他的應用場合。
一個典型的應用是使用泛型改善Java應用系統的DAO層(Data Access Object,數據訪問對象)的數據類型安全。Hibernate是DAO層的應用較廣泛的開發工具集,它是一個開放源代碼的對象關系映射框架,它對JDBC進行了非常輕量級的對象封裝,以便簡單地完成數據庫的訪問操作。但Hibernate由于Jdk的歷史原因,目前沒有在其框架中引入泛型。如要查詢表中的所有數據,沒有泛型的代碼如下:
public List findAll(String hql){
Query q = session.createQuery(hql);
List list = q.list();
return list;}
上面的例子由于沒有泛型的安全檢查,存在如下問題:
1)此函數的調用者將無法知道函數返回值List中的對象是何種數據類型,如果試圖其從List集合中取出對象元素,可能會導致類型轉換錯誤。
2)函數調用者即使知道了集合中的對象類型,取出時還要進行一次類型的強制轉換,將Object轉換成具體的數據類型。
使用泛型改進后的代碼如下:
public List<T> findAll(String hql){
Query q = session.createQuery(hql);
List list = q.list();
return list;}
上面的例子函數要求調用者調用這個函數時,顯式指出要查詢的對象的數據類型,能有效解決類型安全和類型轉換的問題。
4 Java泛型的好處
Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了很大的改動,許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:
4.1 類型安全
泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在非常高的層次上驗證類型假設。沒有泛型,這些假設就只存在于系統開發人員的頭腦中。
Java 程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如“String 列表”或“String 到 String 的映射”。通過在變量聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當作 ClassCastException 展示出來。將類型檢查從運行時挪到編譯時有助于Java開發人員更早、更容易地找到錯誤,并可提高程序的可靠性。
4.2 消除強制類型轉換
泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,并且減少了出錯機會。
盡管減少強制類型轉換可以提高使用泛型類的代碼的累贅程度,但是聲明泛型變量時卻會帶來相應的累贅程度。比較下面兩個代碼例子。
例子1,該代碼不使用泛型:
List li = new ArrayList();
li.put(new Integer(3));
Integer I =(Integer) li.get(0);
例子2,該代碼不使用泛型:
List<Integer> li = new ArrayList<Integer>();
li.put(new Integer(3));
Integer I = li.get(0);
在簡單的程序中使用一次泛型變量不會降低代碼累贅程度。但是對于多次使用泛型變量的大型程序來說,則可以累積起來降低累贅程度。所以泛型消除了強制類型轉換之后,會使得代碼加清晰和簡潔。
4.3 更高的運行效率
在非泛型編程中,將簡單類型作為Object傳遞時會引起Boxing(裝箱)和Unboxing(拆箱)操作,這兩個過程都是具有很大開銷的。引入泛型后,就不必進行Boxing和Unboxing操作了,所以運行效率相對較高,特別在對集合操作非常頻繁的系統中,這個特點帶來的性能提升更加明顯。
4.4 潛在的性能收益
泛型為較大的優化帶來可能。在泛型的初始實現中,編譯器將強制類型轉換(沒有泛型的話,Java系統開發人員會指定這些強制類型轉換)插入生成的字節碼中。但是更多類型信息可用于編譯器這一事實,為未來版本的 JVM 的優化帶來可能。
5 結束語
Java泛型實現了數據類型的預定義,在定義之后再使用它,在編譯時,使用預定義的數據類型檢查相關操作涉及到的對象類型,從而大大改善了非泛型程序中的類型安全問題,使得類型安全錯誤可以及早、簡單地被編譯器發現。為Java系統開發人員開發更高效率、更安全的系統提供了一種更有效的途徑。
參考文獻:
[1] Eckel B. Java編程思想[M].4版.陳昊鵬,譯.北京:機械工業出版社,2007.
[2] Bracha G. Generics in the Java Programming Language[M].O'Reilly,2004.
[3] Naftalin M. Philip Wadler.Java Generics and Collections[M].O'Reilly,2006.
[4] 苗錫奎.基于Java泛型對Spring中強制類型轉換工廠的改進[EB/OL].(2007-10-29)http://www.paper.edu.cn.
[5] 孫斌.面向對象、泛型程序設計與類型約束檢查[J].計算機學報,2004(11):53-65.
[6] Barron M, Stansifer R. A Comparison of Generics in Java and C#[C]//Proc of the 41st ACM SE Regional Conf,2003.