“JDK1.5”的一個重要主題就是通過新增一些特性來簡化開發,這些特性包括泛型,for-each 循環,自動裝包/拆包,枚舉,可變參數, 靜態導入 。使用這些特性有助於我們編寫更加清晰,精悍,安全的代碼。
一. 首先簡單介紹一下各種特性及其使用
1.泛型(Generic)
C++通過模板技術可以指定集合的元素類型,而Java在1.5之前一直沒有相對應的功能。一個集合可以放任何類型的對象,相應地從集合裏面拿對象的時候我們也不得不對他們進行強制得類型轉換。猛虎引入了泛型,它允許指定集合裏元素的類型,這樣你可以得到強類型在編譯時刻進行類型檢查的好處。
“JDK1.5”的一個重要主題就是通過新增一些特性來簡化開發,這些特性包括泛型,for-each 循環,自動裝包/拆包,枚舉,可變參數, 靜態導入 。使用這些特性有助於我們編寫更加清晰,精悍,安全的代碼。
一. 首先簡單介紹一下各種特性及其使用
1.泛型(Generic)
C++通過模板技術可以指定集合的元素類型,而Java在1.5之前一直沒有相對應的功能。一個集合可以放任何類型的對象,相應地從集合裏面拿對象的時候我們也不得不對他們進行強制得類型轉換。猛虎引入了泛型,它允許指定集合裏元素的類型,這樣你可以得到強類型在編譯時刻進行類型檢查的好處。
1 Collection<String> c = new ArrayList();
2 c.add(new Date());
編譯器會給出一個錯誤:
add(java.lang.String) in java.util.Collection<java.lang.String> cannot be applied to (java.util.Date)
2.For-Each循環
For-Each循環得加入簡化了集合的遍歷。假設我們要遍歷一個集合對其中的元素進行一些處理。典型的代碼爲:
[java]
void processAll(Collection c){
for(Iterator i=c.iterator(); i.hasNext();){
MyClass myObject = (MyClass)i.next();
myObject.process();
}
void processAll(Collection c){
for(Iterator i=c.iterator(); i.hasNext();){
MyClass myObject = (MyClass)i.next();
myObject.process();
}
}
使用For-Each循環,我們可以把代碼改寫成:
[java]
void processAll(Collection<MyClass> c){
for (MyClass myObject :c){
myObject.process();
}
}
void processAll(Collection<MyClass> c){
for (MyClass myObject :c){
myObject.process();
}
}
這段代碼要比上面清晰許多,並且避免了強制類型轉換。
3.自動裝包/拆包(Autoboxing/unboxing)
自動裝包/拆包大大方便了基本類型數據和它們包裝類地使用。
自動裝包:基本類型自動轉爲包裝類.(int >> Integer)
自動拆包:包裝類自動轉爲基本類型.(Integer >> int)
在JDK1.5之前,我們總是對集合不能存放基本類型而耿耿於懷,現在自動轉換機制解決了我們的問題。
[java]
int a = 3;
Collection c = new ArrayList();
c.add(a);//自動轉換成Integer.
Integer b = new Integer(2);
c.add(b + 2);
int a = 3;
Collection c = new ArrayList();
c.add(a);//自動轉換成Integer.
Integer b = new Integer(2);
c.add(b + 2);
這裏Integer先自動轉換爲int進行加法運算,然後int再次轉換爲Integer.
4.枚舉(Enums)
JDK1.5加入了一個全新類型的“類”-枚舉類型。爲此JDK1.5引入了一個新關鍵字enmu. 我們可以這樣來定義一個枚舉類型。
[java]
public enum Color
{
Red,
White,
Blue
}
public enum Color
{
Red,
White,
Blue
}
然後可以這樣來使用Color myColor = Color.Red.
枚舉類型還提供了兩個有用的靜態方法values()和valueOf(). 我們可以很方便地使用它們,例如
1 for (Color c : Color.values())
2 System.out.println(c);
5.可變參數(Varargs)
可變參數使程序員可以聲明一個接受可變數目參數的方法。注意,可變參數必須是函數聲明中的最後一個參數。假設我們要寫一個簡單的方法打印一些對象,
util.write(obj1);
util.write(obj1,obj2);
util.write(obj1,obj2,obj3);
…
在JDK1.5之前,我們可以用重載來實現,但是這樣就需要寫很多的重載函數,顯得不是很有效。如果使用可變參數的話我們只需要一個函數就行了
1 public void write(Object... objs) {
2 for (Object obj: objs)
3 System.out.println(obj);
4 }
在引入可變參數以後,Java的反射包也更加方便使用了。對於c.getMethod("test", new Object[0]).invoke(c.newInstance(), new Object[0])),現在我們可以這樣寫了c.getMethod("test").invoke(c.newInstance()),這樣的代碼比原來清楚了很多。
6.靜態導入(Static Imports)
要使用用靜態成員(方法和變量)我們必須給出提供這個方法的類。使用靜態導入可以使被導入類的所有靜態變量和靜態方法在當前類直接可見,使用這些靜態成員無需再給出他們的類名。
import static java.lang.Math.*;
…….
r = sin(PI * 2); //無需再寫r = Math.sin(Math.PI);
不過,過度使用這個特性也會一定程度上降低代碼地可讀性。
二. 重點講一下泛型
在已發佈的Java1.4中在覈心代碼庫中增加了許多新的API(如Loging,正則表達式,NIO)等,在最新發布的JDK1.5和即將發佈的JDK1.6中也新增了許多API,其中比較有重大意義的就是Generics(範型)。
1.什麼是Generics?
Generics可以稱之爲參數類型(parameterized types),由編譯器來驗證從客戶端將一種類型傳送給某一對象的機制。如Java.util.ArrayList,編譯器可以用Generics來保證類型安全。
在我們深入瞭解Generics之前,我們先來看一看當前的java集合框架(Collection)。在j2SE1.4中所有集合的Root Interface是Collection
[java]
protected void collectionsExample() {
ArrayList list = new ArrayList();
list.add(new String("test string"));
list.add(new Integer(9)); // purposely placed here to create a runtime ClassCastException
inspectCollection(list);
}
protected void inspectCollection(Collection aCollection) {
Iterator i = aCollection.iterator();
while (i.hasNext()) {
String element = (String) i.next();
}
}
protected void collectionsExample() {
ArrayList list = new ArrayList();
list.add(new String("test string"));
list.add(new Integer(9)); // purposely placed here to create a runtime ClassCastException
inspectCollection(list);
}
protected void inspectCollection(Collection aCollection) {
Iterator i = aCollection.iterator();
while (i.hasNext()) {
String element = (String) i.next();
}
}
以上的樣例程序包含的兩個方法,collectionExample方法建立了一個簡單的集合類型ArrayList,並在ArrayList中增加了一個String和一個Integer對象.而在inspecCollection方法中,我們迭代這個ArrayList用String進行Cast。我們看第二個方法,就出現了一個問題,Collection在內部用的是Object,而我們要取出Collection中的對象時,需要進行Cast,那麼開發者必需用實際的類型進行Cast,像這種向下造型,編譯器無
法進行檢查,如此一來我們就要冒在代碼在運行拋出ClassCastException的危險。我們看inspecCollection方法,編譯時沒有問題,但在運行時就會拋出ClassCastException異常。所以我們一定要遠離這個重大的運行時錯誤
2.使用Generics
從上一章節中的CassCastException這種異常,我們期望在代碼編譯時就能夠捕捉到,下面我們使用範型修改上一章的樣例程序。
[java]
protected void collectionsExample() {
ArrayList<String> list = new ArrayList<String>();
list.add(new String("test string"));
// list.add(new Integer(9)); this no longer compiles
inspectCollection(list);
}
protected void inspectCollection(Collection<String> aCollection) {
Iterator<String> i = aCollection.iterator();
while(i.hasNext()) {
String element = i.next();
}
}
protected void collectionsExample() {
ArrayList<String> list = new ArrayList<String>();
list.add(new String("test string"));
// list.add(new Integer(9)); this no longer compiles
inspectCollection(list);
}
protected void inspectCollection(Collection<String> aCollection) {
Iterator<String> i = aCollection.iterator();
while(i.hasNext()) {
String element = i.next();
}
}
從上面第2行我們在創建ArrayList時使用了新語法,在JDK1.5中所有的Collection都加入了Generics的聲明。例:
[java]
public class ArrayList<E> extends AbstractList<E> {
// details omitted...
public void add(E element) {
// details omitted
}
public Iterator<E> iterator() {
// details omitted
}
}
public class ArrayList<E> extends AbstractList<E> {
// details omitted...
public void add(E element) {
// details omitted
}
public Iterator<E> iterator() {
// details omitted
}
}
這個E是一個類型變量,並沒有對它進行具體類型的定義,它只是在定義ArrayList時的類型佔位符,在Example 2中的我們在定義ArrayList的實例時用String綁定在E上,當我們用add(E element)方法向ArrayList中增加對象時,那麼就像下面的寫法一樣: public void add(String element);因爲在ArrayList所有方法都會用String來替代E,無論是方法的參數還是返回值。這時我們在看Example 2中的第四行,編譯就會反映出編譯錯誤。
所以在java中增加Generics主要的目的是爲了增加類型安全。
通過上面的簡單的例子我們看到使用Generics的好處有:
· 1.在類型沒有變化時,Collection是類型安全的。
· 2.內在的類型轉換優於在外部的人工造型。
· 3.使Java接口更加強壯,因爲它增加了類型。
· 4.類型的匹配錯誤在編譯階段就可以捕捉到,而不是在代碼運行時。
受約束類型變量
雖然許多Class被設計成Generics,但類型變量可以是受限的
public class C1<T extends Number> { }
public class C2<T extends Person & Comparable> { }
第一個T變量必須繼承Number,第二個T必須繼承Person和實現Comparable
3.Generics方法
像Generics類一樣,方法和構造函數也可以有類型參數。方法的參數的返回值都可以有類型參數,進行Generics。
//Example 4
1 public <T extends Comparable> T max(T t1, T t2) {
2 if (t1.compareTo(t2) > 0)
3 return t1;
4 else return t2;
5 }
這裏,max方法的參數類型爲單一的T類型,而T類型繼承了Comparable,max的參數和返回值都有相同的超類。下面的Example 5顯示了max方法的幾個約束。
//Example 5
1 Integer iresult = max(new Integer(100), new Integer(200));
2 String sresult = max("AA", "BB");
3 Number nresult = max(new Integer(100), "AAA"); // does not compile
在Example 5第1行參數都爲Integer,所以返回值也是Integer,注意返回值沒有進行造型。
在Example 5第2行參數都爲String,所以返回值也是String,注意返回值沒有進行造型。以上都調用了同一個方法。
在Example 5第3行產生以下編譯錯誤:
Example.java:10: incompatible types
found : java.lang.Object&java.io.Serializable&java.lang.Comparable<?>
required: java.lang.Number
Number nresult = max(new Integer(100), "AAA");
這個錯誤發生是因爲編譯器無法確定返回值類型,因爲String和Integer都有相同的超類Object,注意就算我們修正了第三行,這行代碼在運行仍然會報錯,因爲比較了不同的對象。
3.通配(Wildcards)
先看以下兩行代碼是否合法:
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
第一行沒問題, 關鍵在第二行代碼, 大多數人會認爲, "一個String的List自然更是一個Object的List", 因此, 第2行沒問題.
好, 接着看以下代碼:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: 試圖將一個Object賦給一個String!
可見, 通過別名lo, 我們能對ls, 一個String的列表, 進行數據操作(特別是插入一個Object), 從而導致ls不僅僅是容納了String對象! 這是Java編譯器不容許的! 編譯時, 第2行會報告一個編譯錯誤的.
通常, 若Foo是Bar的一個子類型(子類或子接口), G是某個泛型聲明, 則G<Foo>並不是G<Bar>的一個子類型.
假定要輸出一個集合中的所有元素. 以下分別是舊版本及新版本(JDK 1.5)中的寫法:
void printCollection(Collection c) {
Iterator i = c.iterator();
for( k = 0; k < c.size(); k++) {
System.out.println( i.next() );
}}
void printCollection(Collection<Object> c) {
for(Object e : c) {
System.out.println(e);
}}
問題在於, 新版本反而不如舊版本更有用些. 因爲舊版本能使用各種類型的集合作爲參數, 但新版本則只能使用Collection<Object>. 而正如上節看到的, Collection<Object>並不是其它各種集合的超類型(父類型).
所有集合的超類型應該寫作: Collection<?>, 讀作: collection of unknown(未知集合), 即一個集合, 其元素類型可以與任何類型相匹配. 因此稱這種類型爲"通配類型".
正確實現上述舊版本的代碼可以這麼寫:
void printCollection(Collection<?> c) {
for(Object e : c) {
System.out.println(e);
}}
這時, 可以用任意類型的集合來調用此方法. 注意在方法體中, 仍然從 c 中讀入元素並賦給了Object, 這是沒有錯誤的, 因此不論類型實參是何種集合, 它的元素都是object. 然而, 如果任意給它增加一個object則是不安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 編譯時的錯誤
由於我們不知道c的元素類型是什麼, 所以不能給它增加一個object. 方法add()接受一個類型E的參數, 而E與集合的元素類型相同. 當類型實參是?時, 它表示"未知的類型", 我們傳遞給add的參數必須是這個"未知類型"的子類型. 不幸的是, 既然類型未知, 也就無法決定其子類型, 於是什麼也不能作爲其參數. 唯一的例外是null, 因爲null是所有類型的一個成員.www.2cto.com
另一方面, 如果給了一個List<?>, 我們可以調用get()方法並使用其返回的元素. 雖然返回的元素類型是"未知類型", 但它總歸是一個object, 因此將get()返回的元素賦給一個Object類型的變量, 或將其傳遞給一個可接受Object的參數都是安全的.