dwys0343

          技術(shù)整理

            BlogJava :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
            1 Posts :: 16 Stories :: 0 Comments :: 0 Trackbacks

          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)烈建議你嘗試一下。

          posted on 2007-03-26 17:44 特蘭克斯 閱讀(705) 評(píng)論(0)  編輯  收藏 所屬分類: J2SE
          主站蜘蛛池模板: 高邮市| 西宁市| 南康市| 泽州县| 手游| 香格里拉县| 贵阳市| 区。| 巴彦淖尔市| 嘉峪关市| 综艺| 乌兰察布市| 建阳市| 常熟市| 林西县| 宝山区| 青冈县| 铜山县| 民丰县| 古蔺县| 黄浦区| 华宁县| 茌平县| 盖州市| 积石山| 英德市| 兴隆县| 永城市| 宁夏| 永寿县| 昌邑市| 左贡县| 临江市| 赤水市| 中西区| 高邮市| 章丘市| 高密市| 周宁县| 台州市| 长泰县|