JDK 1.5版本包含了Java語(yǔ)法方面的主要改進(jìn)。
自從Java 1.0版本首次受到開(kāi)發(fā)人員歡迎以來(lái),Java語(yǔ)言的語(yǔ)法就沒(méi)有發(fā)生過(guò)太大的變化。雖然1.1版本增加了內(nèi)部類和匿名內(nèi)部類,1.4版本增加了帶有新的assert關(guān)鍵字的assertion(斷定)功能,但Java語(yǔ)法和關(guān)鍵字仍然保持不變--像編譯時(shí)常量一樣處于靜態(tài)。它將通過(guò)J2SE 1.5(代號(hào)Tiger)發(fā)生改變。
過(guò)去的J2SE版本主要關(guān)注新類和性能,而Tiger的目標(biāo)則是通過(guò)使Java編程更易于理解、對(duì)開(kāi)發(fā)人員更為友好、更安全來(lái)增強(qiáng)Java語(yǔ)言本身,同時(shí)最大限度地降低與現(xiàn)有程序的不兼容性。該語(yǔ)言中的變化包括generics(泛化)、autoboxing、一個(gè)增強(qiáng)的“for”循環(huán)、 typesafe enums(類型安全的枚舉類型)、一個(gè)靜態(tài)導(dǎo)入工具(static import facility)和varargs。
通過(guò)generics來(lái)改進(jìn)類型檢查
generics使你能夠指定一個(gè)集合中使用的對(duì)象的實(shí)際類型,而不是像過(guò)去那樣只是使用Object。generics也被稱為“參數(shù)化類型”,因?yàn)樵趃enerics中,一個(gè)類的類型接受影響其行為的類型變量。
generics并不是一個(gè)新概念。C++中有模板,但是模板非常復(fù)雜并且會(huì)導(dǎo)致代碼膨脹。C++編碼人員能夠僅使用C++模板,通過(guò)一些小的技巧來(lái)執(zhí)行階乘函數(shù),然后看著編譯器生成C++源代碼來(lái)處理模板調(diào)用。Java開(kāi)發(fā)人員已經(jīng)從C++語(yǔ)言中學(xué)到了很多關(guān)于generics的知識(shí),并經(jīng)過(guò)了足夠長(zhǎng)時(shí)間的實(shí)踐,知道如何正確使用它們。Tiger的當(dāng)前計(jì)劃是從健壯的Generic Java (GJ)方案演變而來(lái)的。GJ方案的口號(hào)是“使Java的類型簡(jiǎn)化、再簡(jiǎn)化。”
為了了解generics,讓我們從一個(gè)不使用generics的例子開(kāi)始。下面這段代碼以小寫字母打印了一個(gè)字符串集合:
//獲得一個(gè)字符串集合 public void lowerCase(Collection c) { Iterator itr = c.iterator(); while (itr.hasNext()) { String s = (String) itr.next(); System.out.println(s.toLowerCase()); } }
這個(gè)方法不保證只接收字符串。編程人員負(fù)責(zé)記住傳給這個(gè)方法什么類型的變量。Generics通過(guò)顯式聲明類型來(lái)解決這個(gè)問(wèn)題。Generics證明并執(zhí)行了關(guān)于集合包含什么東西的規(guī)則。如果類型不正確,編譯器就會(huì)產(chǎn)生一個(gè)錯(cuò)誤。在下面的改寫代碼中,注意Collection和Iterator是如何聲明它們只接收字符串對(duì)象的:
public void lowerCase( Collection<String> c) { Iterator<String> itr = c.iterator(); while (itr.hasNext()) { System.out.println( itr.next().toLowerCase()); } }
現(xiàn)在,該代碼包含了更強(qiáng)大的類型,但它仍然包含許多鍵盤類型。我們將在后面加以介紹。注意,你可以存儲(chǔ)類型參數(shù)的任何子類型。接下來(lái),我們將使用這個(gè)特性draw()一個(gè)形狀集合。
// 獲得孩子集合... public void drawAll(Collection<Shape> c) { Iterator<Shape> itr = c.iterator(); while (itr.hasNext()) { itr.next().draw(); } }
尖括號(hào)中的值被稱為類型變量。參數(shù)化類型能夠支持任何數(shù)量的類型變量。例如,java.util.Map就支持兩個(gè)類型變量--一個(gè)用于鍵類型,一個(gè)用于值類型。下面的例子使用了一個(gè)帶有指向一列元素對(duì)象的字符串查找鍵的map:
public static void main(String[] args) { HashMap<String, List<Element>> map = new HashMap<String, List<Element>>(); map.put("root", new ArrayList<Element>()); map.put("servlet", new LinkedList<Element>()); }
這個(gè)類定義聲明了它支持多少個(gè)類型變量。類型參數(shù)的數(shù)量必須精確地與所期望的相匹配。而且,類型變量一定不能是原始類型(primitive types)。
List<String, String> // takes one List<int> // 無(wú)效的,原始類型
即使在期望使用一個(gè)普通類型(raw type)的時(shí)候,你也可以使用一個(gè)參數(shù)化類型。當(dāng)然,你也可以反過(guò)來(lái)做,但這么做會(huì)收到一條編譯時(shí)警告:
public static void oldMethod(List list) { System.out.println(list.size()); } public static void main(String[] args) { List<String> words = new ArrayList<String>(); oldMethod(words); // 沒(méi)問(wèn)題 }
這就實(shí)現(xiàn)了輕松的向后兼容:接受一個(gè)原始列表的老方法能夠直接接受一個(gè)參數(shù)化List<String>。接受參數(shù)化List<String>的新方法也能夠接受一個(gè)原始列表,但是因?yàn)樵剂斜聿宦暶骰驁?zhí)行相同的類型約束,所以這個(gè)動(dòng)作會(huì)觸發(fā)一個(gè)警告。可以保證的是:如果在編譯時(shí)你沒(méi)有得到名為unchecked(未檢查)的警告,那么在運(yùn)行時(shí)編譯器生成的強(qiáng)制類型轉(zhuǎn)換(cast)將不會(huì)失敗。
有趣的是,參數(shù)化類型和普通類型被編譯為相同的類型。沒(méi)有專門的類來(lái)指定這一點(diǎn),使用編譯器技巧就可以完成這一切。instanceof檢查可以證明這一點(diǎn)。
words instanceof List // true words instanceof ArrayList //true words instanceof ArrayList<String> // true
這個(gè)檢查產(chǎn)生了一個(gè)問(wèn)題:“如果它們是相同的類型,這種檢查能起多大作用?”這是一條用墨水而不是用血寫的約束。這段代碼將產(chǎn)生一個(gè)編譯錯(cuò)誤,因?yàn)槟悴荒芟騆ist<String>中添加新的Point:
List<String> list = new ArrayList<String>(); list.add(new Point()); // 編譯錯(cuò)誤
但是這段代碼被編譯了!
List<String> list = new ArrayList<String>(); ((List)list).add(new Point());
它將參數(shù)化類型強(qiáng)制轉(zhuǎn)換為一個(gè)普通類型,這個(gè)普通類型是合法的,避免了類型檢查,但正如前面所解釋的那樣,卻產(chǎn)生了一個(gè)調(diào)用未檢查的警告:
warning: unchecked call to add(E) as a member of the raw type java.util.List ((List)list).add(new Point()); ^
寫一個(gè)參數(shù)化類型
Tiger提供了一個(gè)寫參數(shù)化類型的新語(yǔ)法。下面顯示的Holder類可以存放任意引用類型。這樣的類很便于使用,例如,通過(guò)引用語(yǔ)義支持CORBA傳遞,而不需要生成單獨(dú)的Holder類:
public class Holder<A> { private A value; Holder(A v) { value = v; } A get() { return value; } void set(A v) { value = v; } }
使用一個(gè)參數(shù)化的Holder類型,你能夠安全地得到和設(shè)置數(shù)據(jù),而不需進(jìn)行強(qiáng)制類型轉(zhuǎn)換:
public static void main(String[] args) { Holder<String> holder = new Holder<String>("abc"); String val = holder.get(); // "abc" holder.set("def"); }
“A”類型參數(shù)名可以是任何標(biāo)準(zhǔn)的變量名。它通常是一個(gè)單一的大寫字母。你也可以聲明類型參數(shù)必須能夠擴(kuò)展另一個(gè)類,如下所示:
// 也可以 public class Holder<C extends Child>
關(guān)于是否能夠聲明任何其他的類型參數(shù)仍然存在爭(zhēng)議。你對(duì)generics了解的越深,你需要的特殊規(guī)則就越多,但是特殊規(guī)則越多,generics就會(huì)越復(fù)雜。
Tiger中設(shè)計(jì)用來(lái)保存線程局部變量(thread local variable)的核心類java.lang.ThreadLocal,將可能變得與下面這個(gè) Holder類的作用類似:
public class ThreadLocal<T> { public T get(); public void set(T value); }
我們也將看見(jiàn)java.lang.Comparable的變化,允許類聲明與它們相比較的類型:
public interface Comparable<T> { int compareTo(T o); } public final class String implements Comparable<String> { int compareTo(String anotherString); }
Generics不僅僅用于集合,它們有更為廣泛的用途。例如,雖然你不能基于參數(shù)化類型(因?yàn)樗鼈兣c普通類型沒(méi)有什么不同)進(jìn)行捕捉(catch),但是你可以拋出(throw)一個(gè)參數(shù)化類型。換句話說(shuō),你可以動(dòng)態(tài)地決定throws語(yǔ)句中拋出什么。
下面這段令人思維混亂的代碼來(lái)自generics規(guī)范。該代碼通過(guò)擴(kuò)展Exception的類型參數(shù)E定義了一個(gè) Action接口。Action類有一個(gè)拋出作為E 出現(xiàn)的任何類型的run()方法。然后,AccessController類定義一個(gè)接受Action<E>的靜態(tài)exec()方法,并聲明exec()拋出E。聲明該方法自身是參數(shù)化的需要該方法標(biāo)記(method signature)中的特殊<E extends Exception>。
現(xiàn)在,事情變得有點(diǎn)棘手了。main()方法調(diào)用在Action 實(shí)例(作為一個(gè)匿名內(nèi)部類實(shí)現(xiàn))中傳遞的AccessController.exec()方法。該內(nèi)部類被參數(shù)化,以拋出一個(gè) FileNotFoundException。main()方法有一個(gè)捕捉這一異常類型的catch語(yǔ)句。如果沒(méi)有參數(shù)化類型,你將不能確切地知道run()會(huì)拋出什么。有了參數(shù)化類型,你能夠?qū)崿F(xiàn)一個(gè)泛化的Action類,其中run()方法可以任意實(shí)現(xiàn),并可以拋出任意異常(Exception):
interface Action<E extends Exception> { void run() throws E; } class AccessController { public static <E extends Exception> void exec(Action<E> action) throws E { action.run(); } } public class Main { public static void main(String[] args) { try { AccessController.exec( new Action<FileNotFoundException>() { public void run() throws FileNotFoundException { // someFile.delete(); } }); } catch (FileNotFoundException f) { } } }
協(xié)變返回類型
下面進(jìn)行一個(gè)隨堂測(cè)驗(yàn):下面的代碼是否能夠成功編譯?
class Fruit implements Cloneable { Fruit copy() throws CloneNotSupportedException { return (Fruit)clone(); } } class Apple extends Fruit implements Cloneable { Apple copy() throws CloneNotSupportedException { return (Apple)clone(); } }
答案:該代碼在J2SE 1.4中不能編譯,因?yàn)楦膶懸粋€(gè)方法必須有相同的方法標(biāo)記(包括返回類型)作為它改寫的方法。然而,generics有一個(gè)叫做協(xié)變返回類型的特性,使上面的代碼能夠在Tiger中進(jìn)行編譯。該特性是極為有用的。
例如,在最新的JDOM代碼中,有一個(gè)新的Child接口。Child有一個(gè)detach()方法,返回從其父對(duì)象分離的Child對(duì)象。在Child接口中,該方法當(dāng)然返回Child:
public interface Child { Child detach(); // etc }
當(dāng)Comment類實(shí)現(xiàn)detach()時(shí),它總是返回一個(gè)Comment,但如果沒(méi)有協(xié)變返回類型,該方法聲明必須返回Child:
public class Comment { Child detach() { if (parent != null) parent.removeContent(this); return this; } }
這意味著調(diào)用者一定不要將返回的類型再向下返回到一個(gè)Comment。協(xié)變返回類型允許Comment 中的detach()返回 Comment。只要Comment是Child的子類就行。除了能夠返回Document的DocType和能夠返回Element的EntityRef,該特性對(duì)立刻返回Parent 的Child.getParent()方法也能派上用場(chǎng)。協(xié)變返回類型將確定返回類型的責(zé)任從類的用戶(通過(guò)強(qiáng)制類型轉(zhuǎn)換確認(rèn))轉(zhuǎn)交給類的創(chuàng)建者,只有創(chuàng)建者知道哪些類型彼此之間是真正多態(tài)的。 這使應(yīng)用編程接口(API)的用戶使用起來(lái)更容易,但卻稍微增加了API設(shè)計(jì)者的負(fù)擔(dān)。
Autoboxing
Java有一個(gè)帶有原始類型和對(duì)象(引用)類型的分割類型系統(tǒng)。原始類型被認(rèn)為是更輕便的,因?yàn)樗鼈儧](méi)有對(duì)象開(kāi)銷。例如,int[1024]只需要4K存儲(chǔ)空間,以及用于數(shù)組自身的一個(gè)對(duì)象。然而,引用類型能夠在不允許有原始類型的地方被傳遞,例如,傳遞到一個(gè)List。這一限制的標(biāo)準(zhǔn)工作場(chǎng)景是在諸如list.add(new Integer(1))的插入操作之前,將原始類型與其相應(yīng)的引用類型封裝(box或wrap)在一起,然后用諸如((Integer)list.get(0)).intValue()的方法取出(unbox)返回值。
新的 autoboxing特性使編譯器能夠根據(jù)需要隱式地從int轉(zhuǎn)換為Integer,從char 轉(zhuǎn)換為Character等等。auto-unboxing進(jìn)行相反的操作。在下面的例子中,我不使用autoboxing計(jì)算一個(gè)字符串中的字符頻率。我構(gòu)造了一個(gè)應(yīng)該將字符型映射為整型的Map,但是由于Java的分割類型系統(tǒng),我不得不手動(dòng)管理Character和Integer封箱轉(zhuǎn)換(boxing conversions)。
public static void countOld(String s) { TreeMap m = new TreeMap(); char[] chars = s.toCharArray(); for (int i=0; i < chars.length; i++) { Character c = new Character(chars[i]); Integer val = (Integer) m.get(c); if (val == null) val = new Integer(1); else val = new Integer(val.intValue()+1); m.put(c, val); } System.out.println(m); }
Autoboxing使我們能夠編寫如下代碼:
public static void countNew(String s) { TreeMap<Character, Integer> m = new TreeMap<Character, Integer>(); char[] chars = s.toCharArray(); for (int i=0; i < chars.length; i++) { char c = chars[i]; m.put(c, m.get(c) + 1); // unbox } System.out.println(m); }
這里,我重寫了map,以使用generics,而且我讓autoboxing給出了map能夠直接存儲(chǔ)和檢索char 和int值。不幸的是,上面的代碼有一個(gè)問(wèn)題。如果m.get(c)返回空值(null),會(huì)發(fā)生什么情況呢?怎樣取出null值?在搶鮮版(early access release)(參見(jiàn)下一步)中,取出一個(gè)空的Integer 會(huì)返回0。自搶鮮版起,專家組決定取出null值應(yīng)該拋出一個(gè)NullPointerException。因此,put()方法需要被重寫,如下所示:
m.put(c, Collections.getWithDefault( m, c) + 1);
新的Collections.getWithDefault()方法執(zhí)行g(shù)et()函數(shù),在該方法中,如果值為空值,它將返回期望類型的默認(rèn)值。對(duì)于一個(gè)int類型來(lái)說(shuō),則返回0。
雖然autoboxing有助于編寫更好的代碼,但我的建議是謹(jǐn)慎地使用它。封箱轉(zhuǎn)換仍然會(huì)進(jìn)行并仍然會(huì)創(chuàng)建許多包裝對(duì)象(wrapper-object)實(shí)例。當(dāng)進(jìn)行計(jì)數(shù)時(shí),采用將一個(gè)int與一個(gè)長(zhǎng)度為1的的 int數(shù)組封裝在一起的舊方法更好。然后,你可以將該數(shù)組存儲(chǔ)在任何需要引用類型的地方,獲取intarr[0]的值并使用intarr[0]++遞增。你甚至不必再次調(diào)用put(),因?yàn)闀?huì)在適當(dāng)?shù)奈恢卯a(chǎn)生增量。使用這一方法和其他一些方法,你能夠更有效地進(jìn)行計(jì)數(shù)。使用下面的算法,執(zhí)行100萬(wàn)個(gè)字符的對(duì)時(shí)間會(huì)從650毫秒縮短為30毫秒:
public static void countFast(String s) { int[] counts = new int[256]; char[] chars = s.toCharArray(); for (int i=0; i < chars.length; i++) { int c = (int) chars[i]; counts[c]++; // no object creation } for (int i = 0; i < 256; i++) { if (counts[i] > 0) { System.out.println((char)i + ":" + counts[i]); } } }
在C#中,我們可以看到一個(gè)類似但稍微不同的方法。C#有一個(gè)統(tǒng)一的類型系統(tǒng),在這個(gè)系統(tǒng)中值類型和引用類型都擴(kuò)展System.Object。但是,你不能直接看到這一點(diǎn),因?yàn)镃#為簡(jiǎn)單的值類型提供了別名和優(yōu)化。int是System.Int32的一個(gè)別名,short是System.Int16的一個(gè)別名,double是System.Double的一個(gè)別名。在C#中,你能夠調(diào)用“int i = 5; i.ToString();”,它是完全合法的。這是因?yàn)槊總€(gè)值類型都有一個(gè)在它被轉(zhuǎn)換為引用類型時(shí)創(chuàng)建的相應(yīng)隱藏引用類型(在值類型被轉(zhuǎn)換為一個(gè)引用類型時(shí)創(chuàng)建的)。
int x = 9; object o = x; //創(chuàng)建了引用類型 int y = (int) o;
當(dāng)基于一個(gè)不同的類型系統(tǒng)時(shí),最終結(jié)果與我們?cè)贘2SE 1.5中看到的非常接近。
?
對(duì)于循環(huán)的增強(qiáng)
還記得前面的這個(gè)例子么?
public void drawAll(Collection<Shape> c) { Iterator<Shape> itr = c.iterator(); while (itr.hasNext()) { itr.next().draw(); } }
你再也不用輸入這么多的文字了!這里是Tiger版本中的新格式。
public void drawAll(Collection<Shape> c) { for (Shape s : c) { s.draw(); } }
你可以閱讀這樣一段代碼“foreach Shape s in c”。我們注意到設(shè)計(jì)者非常聰明地避免添加任何新的關(guān)鍵字。考慮到很多人都用“in”來(lái)輸入數(shù)據(jù)流,我們對(duì)此應(yīng)該感到非常高興。編譯器將該新的語(yǔ)法自動(dòng)擴(kuò)展到其迭代表中。
for (Iterator<Shape> $i = c.iterator(); $i.hasNext(); ) { Shape s = $i.next(); s.draw(); }
你可以使用該語(yǔ)法來(lái)對(duì)普通(raw,非參數(shù)化的)類型進(jìn)行迭代,但是編譯器會(huì)輸出一個(gè)警告,告訴你必須的類型轉(zhuǎn)換可能會(huì)失敗。你可以在任何數(shù)組和對(duì)象上使用“foreach”來(lái)實(shí)現(xiàn)新的接口java.lang.Iterable。
public interface Iterable<T> { SimpleIterator<T> iterator(); } public interface SimpleIterator<T> { boolean hasNext(); T next(); }
java.lang中的新的接口避免對(duì)java.util的任何語(yǔ)言依賴性。Java語(yǔ)言在java.lang之外必須沒(méi)有依賴性。要注意通過(guò)next()方法來(lái)更巧妙地使用協(xié)變返回類型。需要說(shuō)明的一點(diǎn)是,利用該“foreach”語(yǔ)法和SimpleIterator接口,就會(huì)喪失調(diào)用iterator.remove()的能力。如果你還需要該項(xiàng)能力,則必須你自己迭代該集合。
與C#對(duì)比一下,我們會(huì)看到相似的語(yǔ)法,但是C#使用“foreach”和“in”關(guān)鍵字,從最初版本開(kāi)始它們就被作為保留字。
// C# foreach (Color c in colors) { Console.WriteLine(c); }
C#的“foreach”對(duì)任何集合(collection)或者數(shù)組以及任何可列舉的實(shí)現(xiàn)都有效。我們?cè)僖淮慰吹搅嗽贘ava和C#之間的非常相似之處。
(類型安全的枚舉類型)typesafe enum
enums 是定義具有某些命名的常量值的類型的一種方式。你在C,C++中已經(jīng)見(jiàn)過(guò)它們,但是顯然,它們?cè)?jīng)在Java中不用。現(xiàn)在,經(jīng)過(guò)了八年之后,Java重又采用它們,并且大概比先前的任何語(yǔ)言都使用得更好。讓我們首先來(lái)看看先前我們是如何解決enum問(wèn)題的?不知道你有沒(méi)有編寫過(guò)如下代碼?
class PlayingCard { public static final int SUIT_CLUBS = 0; public static final int SUIT_DIAMONDS = 1; public static final int SUIT_HEARTS = 2; public static final int SUIT_SPADES = 3; // ... }
這段代碼很簡(jiǎn)單也很常見(jiàn),但是它有問(wèn)題。首先,它不是類型安全的(typesafe)。可以給一個(gè)方法傳遞文字“5”來(lái)獲取一個(gè)suit,并且將被編譯。同時(shí),這些值用這些常量直接被編譯成每個(gè)類。Java通過(guò)這些常量進(jìn)行這種"內(nèi)聯(lián)"(inlining)來(lái)達(dá)到優(yōu)化的目的,但其風(fēng)險(xiǎn)在于,如果對(duì)這些值重新排序并且只重新編譯該類,則其他類將會(huì)錯(cuò)誤地處理這些suits。 而且,該類型是非常原始的,它不能被擴(kuò)展或者增強(qiáng),同時(shí)如果你輸出這些值中的一個(gè),你只會(huì)得到一個(gè)含意模糊的整型量,而不是一個(gè)好記的有用名字。這種方法非常簡(jiǎn)單,這也正是我們?yōu)槭裁匆@樣做的原因,但是它并不是最好的方法。所以,也許需要嘗試一下下面的這個(gè)方法:
class PlayingCard { class Suit { public static final Suit CLUBS = new Suit(); public static final Suit DIAMONDS = new Suit(); public static final Suit HEARTS = new Suit(); public static final Suit SPADES = new Suit(); protected Suit() { } } }
它是類型安全的(typesafe)且更加具有可擴(kuò)展性,并且,屬于面向?qū)ο笤O(shè)計(jì)的類。然而,這樣簡(jiǎn)單的一種方法并不支持序列化,沒(méi)有合法值的列表,無(wú)法將這些值排序,并且,不能作為一個(gè)有意義的字符串來(lái)打印一個(gè)值。你當(dāng)然可以添加這些特性,Josh Bloch在他的Effective Java一書(shū)中(第五章,第21條)為我們準(zhǔn)確展示了如何解決這些問(wèn)題。然而,你最終得到的是幾頁(yè)蹩腳的代碼。
Java新的enum特性具有一個(gè)簡(jiǎn)單的單行語(yǔ)法:
class PlayingCard { public enum Suit { clubs, diamonds, hearts, spades } }
被稱之為enum(枚舉)類型的該suit,對(duì)應(yīng)于每個(gè)enum常量都有一個(gè)成員項(xiàng)。每個(gè)enum類型都是一個(gè)實(shí)際類,它可以自動(dòng)擴(kuò)展新類java.lang.Enum。編譯器賦予enum類以有意義的String()、 hashCode(), 和equals() 方法, 并且自動(dòng)提供Serializable(可序列化的)和Comparable(可比較的)能力。令人高興地是enum類的聲明是遞歸型的:
public class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
使用最新的enum類型可以提供很多好處:包括:比整型操作(int operations)更好的性能,編譯時(shí)更好的類型安全性,不會(huì)被編譯到客戶端并且可以被重新命名和排序的常量,打印的值具有含意清晰的信息,能夠在集合(collections)甚至switch中被使用,具有添加域(fields)和方法的能力,以及實(shí)現(xiàn)任意接口的能力。
每個(gè)enum具有一個(gè)字符串名字和一個(gè)整型順序號(hào)值:
out.println(Suit.clubs); // "clubs" out.println(Suit.clubs.name); // "clubs" out.println(Suit.clubs.ordinal); // 0 out.println(Suit.diamonds.ordinal); // 1 Suit.clubs == Suit.clubs // true Suit.clubs == Suit.diamonds // false Suit.clubs.compareTo(Suit.diamonds) // -1
enum可以擁有構(gòu)造器和方法。甚至一個(gè)main()方法都是合法的。下面的例子將值賦給羅馬數(shù)字:
public enum Roman { I(1), V(5), X(10), L(50), C(100), D(500), M(1000); private final int value; Roman(int value) { this.value = value; } public int value() { return value; } public static void main(String[] args) { System.out.println(Roman.I); } }
非常奇怪的是,不能將序列數(shù)值賦給一個(gè)enum,比如說(shuō)“enum Month{jan=1,feb=2,….}”。 然而,卻可以給enum常量添加行為。比如說(shuō),在JDOM中,XMLOutputter支持?jǐn)?shù)種空白處理方法。如果JDOM是參照J(rèn)2SE1.5構(gòu)建的,那么這些方法就可以用一個(gè)enum來(lái)定義,并且enum類型本身可以具有這種處理行為。不管這種編碼模式是不是會(huì)被證明是有用的,我們都會(huì)逐漸了解它。肯定這是一個(gè)異常有趣的概念。
public abstract enum Whitespace { raw { String handle(String s) { return s; } }, trim { String handle(String s) { return s.trim(); } }, trimFullWhite { String handle(String s) { return s.trim().equals("") ? "":s; } }; abstract String handle(String s); public static void main(String[] args) { String sample = " Test string "; for (Whitespace w : Whitespace.VALUES) System.out.println(w + ": '" + w.handle(sample) + "'"); }}
很少有公開(kāi)的出版物談及Java新的enum。enum常量名是不是都應(yīng)該都大寫?對(duì)于常量來(lái)說(shuō),這是一個(gè)標(biāo)準(zhǔn),但是規(guī)范指出小寫名稱“于更好的字符串格式,一般應(yīng)該避免使用定制的toString方法。”另外,名字和順序號(hào)該是域還是方法?這是封裝方法一再引起爭(zhēng)論的問(wèn)題。在向J2SE1.5添加關(guān)鍵字方面,該特性也落了一個(gè)不太好的名聲。令人傷心的是,它還是一個(gè)通常被用作存儲(chǔ)Enumerator(計(jì)數(shù)器)實(shí)例的詞。如果你已經(jīng)在你的代碼中使用了“enum”,那么在你為J2SE 1.5的應(yīng)用編譯之前,必須修改它。現(xiàn)在,你已得到了充分的警示。
讓我們看一下C#,所有的enum都擴(kuò)展成System.Enum。每個(gè)enum都具有可以被賦值的整型(或者字節(jié)型或者其他類型)值。enum還擁有靜態(tài)方法,以便從字符串常量來(lái)初始化enum,獲取有效值列表,從而,可以看到某個(gè)值是不是被支持。通過(guò)使用[flags]屬性來(lái)標(biāo)記一個(gè)enum,你可以確保值支持位屏蔽,并且系統(tǒng)負(fù)責(zé)打印被屏蔽的值的有用輸出:
// C# [Flags] public enum Credit : byte { Visa = 1, MC = 2, Discover = 4 } Credit accepted = Credit.Visa | Credit.MC; c.WriteLine(accepted); // 3 c.WriteLine(accepted.Format());//"Visa|MC"
靜態(tài)導(dǎo)入
靜態(tài)導(dǎo)入使得我們可以將一套靜態(tài)方法和域放入作用域(scope)。它是關(guān)于調(diào)用的一種縮寫,可以忽略有效的類名。比如說(shuō),對(duì)Math.abs(x)的調(diào)用可以被簡(jiǎn)單地寫成 abs(x)。為了靜態(tài)地導(dǎo)入所有的靜態(tài)域和方法,我們可以使用“import static java.lang.Math”,或者指定要導(dǎo)入的具體內(nèi)容,而使用“import static java.lang.System.out”--在這里沒(méi)有什么令人激動(dòng)的新特性,只是縮寫而已。它讓你可以不用Math而來(lái)完成math(數(shù)字計(jì)算)。
import static java.lang.Math.*; import static java.lang.System.out; public class Test { public static void main(String[] args) { out.println(abs(-1) * PI); } }
注意,“static”關(guān)鍵字的重用是為了避免任何新的關(guān)鍵詞。語(yǔ)言越成熟,對(duì)于“static”關(guān)鍵詞的使用就越多。如果在靜態(tài)成員之間發(fā)生沖突的話,就會(huì)出現(xiàn)含混的編譯錯(cuò)誤,這一點(diǎn)跟類的導(dǎo)入一樣。是否將java.lang.Math.* 作為固有的導(dǎo)入引發(fā)了一定的爭(zhēng)論,不過(guò)在獲知其將會(huì)觸發(fā)含混的錯(cuò)誤之后,這種爭(zhēng)論不會(huì)再發(fā)生了。
Varargs
“varargs”表示“參數(shù)的變量”,存在于C語(yǔ)言中,并且支持通用的printf()和scanf()函數(shù)。 在Java中,我們通過(guò)編寫一些接受Object[]、List、Properties(屬性)的方法以及可以描述多個(gè)值的其它簡(jiǎn)單數(shù)據(jù)結(jié)構(gòu)--比如說(shuō),Method.invoke(Object obj,Object[] args--來(lái)模擬這一特性。)。這要求調(diào)用程序?qū)?shù)據(jù)封裝到這種單一的容器結(jié)構(gòu)中。varargs允許調(diào)用程序傳遞值的任意列表,而編譯器會(huì)為接收程序?qū)⑵滢D(zhuǎn)化為數(shù)組。其語(yǔ)法就是在參數(shù)聲明中的參數(shù)名之后添加“...”,以便使其成為vararg。它必須是最后一個(gè)參數(shù)--比如說(shuō),編寫一個(gè)sum()函數(shù)以便將任意數(shù)量的整數(shù)相加:
out.println(sum(1, 2, 3)); public static int sum(int args...) { int sum = 0; for (int x : args) { sum += x; } return sum; }
在搶鮮版本中,vararg符號(hào)使用方括號(hào),就像sum(int[] args...)。然而,在之后的討論中,根據(jù)James Gosling的提議,方括號(hào)被去掉了。在這里的例子中,我們不使用方括號(hào),但是如果你需要在搶鮮版本中使用這些代碼的話,就需要將方括號(hào)添加上去。借助autoboxing,可以通過(guò)接受一個(gè)Object args…,可以接受任何類型的參數(shù),包括原始類型。這與printf()類型的一些方法一樣,它們接受任何數(shù)量的所有類型的參數(shù)。實(shí)際上,該Tiger版本可以使用這一特性通過(guò)format方法(其行為與printf()一樣)來(lái)提供一個(gè)Formattable(可格式化)的接口。這是我們?cè)谝院蟮奈恼轮袑⒁懻摰脑掝}。目前,我們只編寫簡(jiǎn)單的printf():
public static void printf(String fmt, int args...) { int i = 0; for (char c : fmt.toCharArray()) { out.print(c == '%' ? args[i++] : c); } } public static void main(String[] args) { printf("My values are % and % ", 1, 2); } 在Tiger版本中,你會(huì)發(fā)現(xiàn)采用一個(gè)新格式的invoke()函數(shù): invoke(Object obj, Object args...). 這看上去更加自然。結(jié)論
J2SE1.5版努力使Java的編程更加簡(jiǎn)便、安全和更加富有表現(xiàn)力。這些特性和諧完美的被結(jié)合在一起。如果你跟我一樣,總是喜歡用老的“for”循環(huán),你肯定希
望你擁有Tiger。然而,需要記住的是,該規(guī)范并沒(méi)有最終完成,很多地方還需要修改。管理這些變化的專家小組(JSR-14,JSR-175以及JSR-201)會(huì)在2003年
年末的beta版本發(fā)布之前,以及預(yù)期在2004年發(fā)布最終版發(fā)布之前,會(huì)做出很多修改。然而,Sun表達(dá)了對(duì)JavaOne的信心,認(rèn)為總體上主要原則不會(huì)改變太多。
如果你想體驗(yàn)一下,那么你可以從下面的站點(diǎn)獲取搶鮮版本。 從中你會(huì)找到可能從任何一個(gè)預(yù)覽版軟件都會(huì)遇到的錯(cuò)誤,但是也會(huì)看到很多激動(dòng)人心的新特性。
我強(qiáng)烈建議你嘗試一下。