無為

          無為則可為,無為則至深!

            BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
            190 Posts :: 291 Stories :: 258 Comments :: 0 Trackbacks

          Java 5.0 發(fā)布了,許多人都將開始使用這個(gè) JDK 版本的一些新增特性。從增強(qiáng)的 for 循環(huán)到諸如泛型 (generic) 之類更復(fù)雜的特性,都將很快出現(xiàn)在您所編寫的代碼中。我們剛剛完成了一個(gè)基于 Java 5.0 的大型任務(wù),而本文就是要介紹我們使用這些新特性的體驗(yàn)。本文不是一篇入門性的文章,而是對(duì)這些特性以及它們所產(chǎn)生的影響的深入介紹,同時(shí)還給出了一些在項(xiàng)目中更有效地使用這些特性的技巧。

            在 JDK 1.5 beta 階段,我們?yōu)?/font> BEA Java IDE 開發(fā)了一個(gè) Java 5 編譯器。因?yàn)槲覀儗?shí)現(xiàn)了許多新特性,所以人們開始以新的方式利用它們 ; 有些用法很聰明,而有些用法明顯應(yīng)該被列入禁用清單。編譯器本身使用了新的語言特性,所以我們也獲得了使用這些特性維護(hù)代碼的直接體驗(yàn)。本文將介紹其中的許多特性和使用它們的體驗(yàn)。

            我們假定您已經(jīng)熟悉了這些新特性,所以不再全面介紹每個(gè)特性,而是談?wù)撘恍┯腥さ摹⒌芸赡懿惶黠@的內(nèi)容和用法。這些技巧出自我們的實(shí)際體驗(yàn),并大致按照語言特性進(jìn)行了分類。

            我們將從最簡(jiǎn)單的特性開始,逐步過渡到高級(jí)特性。泛型所包含的內(nèi)容特別豐富,因此占了本文一半的篇幅。

            增強(qiáng)的 for 循環(huán)

            為了迭代集合和數(shù)組,增強(qiáng)的 for 循環(huán)提供了一個(gè)簡(jiǎn)單、兼容的語法。有兩點(diǎn)值得一提 :

             Init 表達(dá)式

            在循環(huán)中,初始化表達(dá)式只計(jì)算一次。這意味著您通常可以移除一個(gè)變量聲明。在這個(gè)例子中,我們必須創(chuàng)建一個(gè)整型數(shù)組來保存 computeNumbers() 的結(jié)果,以防止每一次循環(huán)都重新計(jì)算該方法。您可以看到,下面的代碼要比上面的代碼整潔一些,并且沒有泄露變量 numbers:

          未增強(qiáng)的 For
          int sum = 0;
          Integer[] numbers = computeNumbers();
          for (int i=0; i < numbers.length ; i++)
          ??? sum += numbers[i];
          增強(qiáng)后的 For
          int sum = 0;

          for ( int number: computeNumbers() )
          ??? sum += number;

            局限性

            有時(shí)需要在迭代期間訪問迭代器或下標(biāo),看起來增強(qiáng)的 for 循環(huán)應(yīng)該允許該操作,但事實(shí)上不是這樣,請(qǐng)看下面的例子 :

          for (int i=0; i < numbers.length ; i++) {
          ??? if (i != 0) System.out.print(",");
          ??? System.out.print(numbers[i]);
          }

            我們希望將數(shù)組中的值打印為一個(gè)用逗號(hào)分隔的清單。我們需要知道目前是否是第一項(xiàng),以便確定是否應(yīng)該打印逗號(hào)。使用增強(qiáng)的 for 循環(huán)是無法獲知這種信息的。我們需要自己保留一個(gè)下標(biāo)或一個(gè)布爾值來指示是否經(jīng)過了第一項(xiàng)。   這是另一個(gè)例子 :

          for (Iterator<integer> it = n.iterator() ; it.hasNext() ; )
          ??? if (it.next() < 0)
          ??????? it.remove();

            在此例中,我們想從整數(shù)集合中刪除負(fù)數(shù)項(xiàng)。為此,需要對(duì)迭代器調(diào)用一個(gè)方法,但是當(dāng)使用增強(qiáng)的 for 循環(huán)時(shí),迭代器對(duì)我們來說是看不到的。因此,我們只能使用 Java 5 之前版本的迭代方法。   順便說一下,這里需要注意的是,由于 Iterator 是泛型,所以其聲明是 Iterator 。許多人都忘記了這一點(diǎn)而使用了 Iterator 的原始格式。

            注釋

            注釋處理是一個(gè)很大的話題。因?yàn)楸疚闹魂P(guān)注核心的語言特性,所以我們不打算涵蓋它所有的可能形式和陷阱。  我們將討論內(nèi)置的注釋 (SuppressWarnings Deprecated Override) 以及一般注釋處理的局限性。

             Suppress Warnings

            該注釋關(guān)閉了類或方法級(jí)別的編譯器警告。有時(shí)候您比編譯器更清楚地知道,代碼必須使用一個(gè)被否決的方法或執(zhí)行一些無法靜態(tài)確定是否類型安全的動(dòng)作,而使用 :

          @SuppressWarnings("deprecation")
          public static void selfDestruct() {
          ??? Thread.currentThread().stop();
          }

            這可能是內(nèi)置注釋最有用的地方。遺憾的是, 1.5.0 _04 javac 不支持它。但是 1.6 支持它,并且 Sun 正在努力將其向后移植到 1.5 中。

             Eclipse 3.1 中支持該注釋,其他 IDE 也可能支持它。這允許您把代碼徹底地從警告中解脫出來。如果在編譯時(shí)出現(xiàn)警告,可以確定是您剛剛把它添加進(jìn)來 —— 以幫助查看那些可能不安全的代碼。隨著泛型的添加,它使用起來將更趁手。

             Deprecated

            遺憾的是, Deprecated 沒那么有用。它本來旨在替換 @deprecated javadoc 標(biāo)簽,但是由于它不包含任何字段,所以也就沒有方法來建議 deprecated 類或方法的用戶應(yīng)該使用什么做為替代品。大多數(shù)用法都同時(shí)需要 javadoc 標(biāo)簽和這個(gè)注釋。

          Override

             Override 表示,它所注釋的方法應(yīng)該重寫超類中具有相同簽名的方法 :

          @Override
          public int hashCode() {
          ??? ...
          }

            看上面的例子,如果沒有在 hashCode 中將 “C” 大寫,在編譯時(shí)不會(huì)出現(xiàn)錯(cuò)誤,但是在運(yùn)行時(shí)將無法像期望的那樣調(diào)用該方法。通過添加 Override 標(biāo)簽,編譯器會(huì)提示它是否真正地執(zhí)行了重寫。

            在超類發(fā)生改變的情況中,這也很有幫助。如果向該方法中添加一個(gè)新參數(shù),而且方法本身也被重命名了,那么子類將突然不能編譯,因?yàn)樗辉僦貙懗惖娜魏螙|西。

            其它注釋

            注釋在其他場(chǎng)景中非常有用。當(dāng)不是直接修改行為而是增強(qiáng)行為時(shí),特別是在添加樣板代碼的情況下,注釋在諸如 EJB Web services 這樣的框架中運(yùn)行得非常好。

            注釋不能用做預(yù)處理器。 Sun 的設(shè)計(jì)特別預(yù)防了完全因?yàn)樽⑨尪薷念惖淖止?jié)碼。這樣可以正確地理解該語言的成果,而且 IDE 之類的工具也可以執(zhí)行深入的代碼分析和重構(gòu)之類的功能。

            注釋不是銀彈。第一次遇到的時(shí)候,人們?cè)噲D嘗試各種技巧。請(qǐng)看下面這個(gè)從別人那里獲得的建議 :

          public class Foo {
          ?
          ??? @Property
          ??? private int bar;
          ?
          }

            其思想是為私有字段 bar 自動(dòng)創(chuàng)建 getter setter 方法。遺憾的是,這個(gè)想法有兩個(gè)失敗之處 :1) 它不能運(yùn)行, 2) 它使代碼難以閱讀和處理。   它是無法實(shí)現(xiàn)的,因?yàn)榍懊嬉呀?jīng)提到了, Sun 特別阻止了對(duì)出現(xiàn)注釋的類進(jìn)行修改。

            即使是可能的,它也不是一個(gè)好主意,因?yàn)樗勾a可讀性差。第一次看到這段代碼的人會(huì)不知道該注釋創(chuàng)建了方法。此外,如果將來您需要在這些方法內(nèi)部執(zhí)行一些操作,注釋也是沒用的。   總之,不要試圖用注釋去做那些常規(guī)代碼可以完成的事情。

            枚舉

             enum 非常像 public static final int 聲明,后者作為枚舉值已經(jīng)使用了很多年。對(duì) int 所做的最大也是最明顯的改進(jìn)是類型安全 —— 您不能錯(cuò)誤地用枚舉的一種類型代替另一種類型,這一點(diǎn)和 int 不同,所有的 int 對(duì)編譯器來說都是一樣的。除去極少數(shù)例外的情況,通常都應(yīng)該用 enum 實(shí)例替換全部的枚舉風(fēng)格的 int 結(jié)構(gòu)。

            枚舉提供了一些附加的特性。 EnumMap EnumSet 這兩個(gè)實(shí)用類是專門為枚舉優(yōu)化的標(biāo)準(zhǔn)集合實(shí)現(xiàn)。如果知道集合只包含枚舉類型,那么應(yīng)該使用這些專門的集合來代替 HashMap HashSet

            大部分情況下,可以使用 enum 對(duì)代碼中的所有 public static final int 做插入替換。它們是可比的,并且可以靜態(tài)導(dǎo)入,所以對(duì)它們的引用看起來是等同的,即使是對(duì)于內(nèi)部類 ( 或內(nèi)部枚舉類型 ) 。注意,比較枚舉類型的時(shí)候,聲明它們的指令表明了它們的順序值。

             隱藏的 靜態(tài)方法

            兩個(gè)靜態(tài)方法出現(xiàn)在所有枚舉類型聲明中。因?yàn)樗鼈兪敲杜e子類上的靜態(tài)方法,而不是 Enum 本身的方法,所以它們?cè)?/font> java.lang.Enum javadoc 中沒有出現(xiàn)。

            第一個(gè)是 values() ,返回一個(gè)枚舉類型所有可能值的數(shù)組。

            第二個(gè)是 valueOf() ,為提供的字符串返回一個(gè)枚舉類型,該枚舉類型必須精確地匹配源代碼聲明。

            方法

            關(guān)于枚舉類型,我們最喜歡的一個(gè)方面是它可以有方法。過去您可能需要編寫一些代碼,對(duì) public static final int 進(jìn)行轉(zhuǎn)換,把它從數(shù)據(jù)庫類型轉(zhuǎn)換為 JDBC URL 。而現(xiàn)在則可以讓枚舉類型本身帶一個(gè)整理代碼的方法。下面就是一個(gè)例子,包括 DatabaseType 枚舉類型的抽象方法以及每個(gè)枚舉實(shí)例中提供的實(shí)現(xiàn) :

          ? public enum? DatabaseType {
          ? ORACLE {
          ? public String getJdbcUrl() {...}
          ? },
          ? MYSQL {
          ? public String getJdbcUrl() {...}
          ? };
          ? public abstract String getJdbcUrl();
          ? }

            現(xiàn)在枚舉類型可以直接提供它的實(shí)用方法。例如 :

          DatabaseType dbType = ...;
          String jdbcURL = dbType.getJdbcUrl();

            要獲取 URL ,必須預(yù)先知道該實(shí)用方法在哪里。

          可變參數(shù) (Vararg)

            正確地使用可變參數(shù)確實(shí)可以清理一些垃圾代碼。典型的例子是一個(gè)帶有可變的 String 參數(shù)個(gè)數(shù)的 log 方法 :

          ??? Log.log(String code)
          ??? Log.log(String code,? String arg)
          ??? Log.log(String code,? String arg1, String arg2)
          ??? Log.log(String code,? String[] args)

            當(dāng)討論可變參數(shù)時(shí),比較有趣的是,如果用新的可變參數(shù)替換前四個(gè)例子,將是兼容的 :

             Log.log(String code, String... args)

            所有的可變參數(shù)都是源兼容的 —— 那就是說,如果重新編譯 log() 方法的所有調(diào)用程序,可以直接替換全部的四個(gè)方法。然而,如果需要向后的二進(jìn)制兼容性,那么就需要舍去前三個(gè)方法。只有最后那個(gè)帶一個(gè)字符串?dāng)?shù)組參數(shù)的方法等效于可變參數(shù)版本,因此可以被可變參數(shù)版本替換。

            類型強(qiáng)制轉(zhuǎn)換

            如果希望調(diào)用程序了解應(yīng)該使用哪種類型的參數(shù),那么應(yīng)該避免用可變參數(shù)進(jìn)行類型強(qiáng)制轉(zhuǎn)換。看下面這個(gè)例子,第一項(xiàng)希望是 String ,第二項(xiàng)希望是 Exception:

          ??? Log.log(Object...? objects) {
          ??? String message = (String)objects[0];
          ??? if (objects.length > 1) {
          ??? Exception e = (Exception)objects[1];
          ??? // Do something with the exception
          ??? }
          ??? }

            方法簽名應(yīng)該如下所示,相應(yīng)的可變參數(shù)分別使用 String Exception 聲明 :

             Log.log(String message, Exception e, Object... objects) {...}

            不要使用可變參數(shù)破壞類型系統(tǒng)。需要強(qiáng)類型化時(shí)才可以使用它。對(duì)于這個(gè)規(guī)則, PrintStream.printf() 是一個(gè)有趣的例外 : 它提供類型信息作為自己的第一個(gè)參數(shù),以便稍后可以接受那些類型。

            協(xié)變返回

            協(xié)變返回的基本用法是用于在已知一個(gè)實(shí)現(xiàn)的返回類型比 API 更具體的時(shí)候避免進(jìn)行類型強(qiáng)制轉(zhuǎn)換。在下面這個(gè)例子中,有一個(gè)返回 Animal 對(duì)象的 Zoo 接口。我們的實(shí)現(xiàn)返回一個(gè) AnimalImpl 對(duì)象,但是在 JDK 1.5 之前,要返回一個(gè) Animal 對(duì)象就必須聲明。 :

          ??? public interface Zoo? {
          ??? public Animal getAnimal();
          ??? }
          ? public class ZooImpl? implements Zoo {
          ? public Animal getAnimal(){
          ? return new AnimalImpl();
          ? }
          ? }

            協(xié)變返回的使用替換了三個(gè)反模式 :

             · 直接字段訪問。為了規(guī)避 API 限制,一些實(shí)現(xiàn)把子類直接暴露為字段 :

             ZooImpl._animal

             · 另一種形式是,在知道實(shí)現(xiàn)的實(shí)際上是特定的子類的情況下,在調(diào)用程序中執(zhí)行向下轉(zhuǎn)換 :

             ((AnimalImpl)ZooImpl.getAnimal()).implMethod();

             · 我看到的最后一種形式是一個(gè)具體的方法,該方法用來避免由一個(gè)完全不同的簽名所引發(fā)的問題 :

             ZooImpl._getAnimal();

            這三種模式都有它們的問題和局限性。要么是不夠整潔,要么就是暴露了不必要的實(shí)現(xiàn)細(xì)節(jié)。

            協(xié)變

            協(xié)變返回模式就比較整潔、安全并且易于維護(hù),它也不需要類型強(qiáng)制轉(zhuǎn)換或特定的方法或字段 :

          public AnimalImpl getAnimal(){
          return new AnimalImpl();
          }
            使用結(jié)果:
          ZooImpl.getAnimal().implMethod();

          使用泛型

            我們將從兩個(gè)角度來了解泛型 : 使用泛型和構(gòu)造泛型。我們不討論 List Set Map 的顯而易見的用法。知道泛型集合是強(qiáng)大的并且應(yīng)該經(jīng)常使用就足夠了。

            我們將討論泛型方法的使用以及編譯器推斷類型的方法。通常這些都不會(huì)出問題,但是當(dāng)出問題時(shí),錯(cuò)誤信息會(huì)非常令人費(fèi)解,所以需要了解如何修復(fù)這些問題。

            泛型方法

            除了泛型類型, Java 5 還引入了泛型方法。在這個(gè)來自 java.util.Collections 的例子中,構(gòu)造了一個(gè)單元素列表。新的 List 的元素類型是根據(jù)傳入方法的對(duì)象的類型來推斷的 :

          static <T> List<T> Collections.singletonList(T o)
          示例用法:
          public List<Integer> getListOfOne() {
          ??? return Collections.singletonList(1);
          }

            示例用法 :

            在示例用法中,我們傳入了一個(gè) int 。所以方法的返回類型就是 List 。編譯器把 T 推斷為 Integer 。這和泛型類型是不同的,因?yàn)槟ǔ2恍枰@式地指定類型參數(shù)。

            這也顯示了自動(dòng)裝箱和泛型的相互作用。類型參數(shù)必須是引用類型 : 這就是為什么我們得到的是 List 而不是 List

            不帶參數(shù)的泛型方法

             emptyList() 方法與泛型一起引入,作為 java.util.Collections EMPTY_LIST 字段的類型安全置換 :

          static <T> List<T> Collections.emptyList()
          示例用法:
          public List<Integer> getNoIntegers() {
          ??? return Collections.emptyList();
          }

            與先前的例子不同,這個(gè)方法沒有參數(shù),那么編譯器如何推斷 T 的類型呢 ? 基本上,它將嘗試使用一次參數(shù)。如果沒有起作用,它再次嘗試使用返回或賦值類型。在本例中,返回的是 List ,所以 T 被推斷為 Integer

            如果在返回語句或賦值語句之外的位置調(diào)用泛型方法會(huì)怎么樣呢 ? 那么編譯器將無法執(zhí)行類型推斷的第二次傳送。在下面這個(gè)例子中, emptyList() 是從條件運(yùn)算符內(nèi)部調(diào)用的 :

          public List<Integer> getNoIntegers() {
          ??? return x ? Collections.emptyList() : null;
          }

            因?yàn)榫幾g器看不到返回上下文,也不能推斷 T ,所以它放棄并采用 Object 。您將看到一個(gè)錯(cuò)誤消息,比如 :“ 無法將 List<Object> 轉(zhuǎn)換為 List<Integer> 為了修復(fù)這個(gè)錯(cuò)誤,應(yīng)顯式地向方法調(diào)用傳遞類型參數(shù)。這樣,編譯器就不會(huì)試圖推斷類型參數(shù),就可以獲得正確的結(jié)果:

          return x ? Collections.<Integer>emptyList() : null;

            這種情況經(jīng)常發(fā)生的另一個(gè)地方是在方法調(diào)用中。如果一個(gè)方法帶一個(gè) List<String> 參數(shù),并且需要為那個(gè)參數(shù)調(diào)用這個(gè)傳遞的 emptyList() ,那么也需要使用這個(gè)語法。
          集合之外

            這里有三個(gè)泛型類型的例子,它們不是集合,而是以一種新穎的方式使用泛型。這三個(gè)例子都來自標(biāo)準(zhǔn)的 Java 庫:

          ??Class<T>
          Class
          在類的類型上被參數(shù)化了。這就使無需類型強(qiáng)制轉(zhuǎn)換而構(gòu)造一個(gè) newInstance 成為可能。
          ??Comparable<T>
          Comparable
          被實(shí)際的比較類型參數(shù)化。這就在 compareTo() 調(diào)用時(shí)提供了更強(qiáng)的類型化。例如, String 實(shí)現(xiàn) Comparable<String> 。對(duì)除 String 之外的任何東西調(diào)用 compareTo() ,都會(huì)在編譯時(shí)失敗。
          ??Enum<E extends Enum<E>>
          Enum
          被枚舉類型參數(shù)化。一個(gè)名為 Color 的枚舉類型將擴(kuò)展 Enum<Color> getDeclaringClass() 方法返回枚舉類型的類對(duì)象,在這個(gè)例子中就是一個(gè) Color 對(duì)象。它與 getClass() 不同,后者可能返回一個(gè)無名類。

          ????? 通配符

            泛型最復(fù)雜的部分是對(duì)通配符的理解。我們將討論三種類型的通配符以及它們的用途。

            首先讓我們了解一下數(shù)組是如何工作的。可以從一個(gè) Integer[] 為一個(gè) Number[] 賦值。如果嘗試把一個(gè) Float 寫到 Number[] 中,那么可以編譯,但在運(yùn)行時(shí)會(huì)失敗,出現(xiàn)一個(gè) ArrayStoreException

          Integer[] ia = new Integer[5];
          Number[] na = ia;
          na[0] = 0.5; // compiles, but fails at runtime

          如果試圖把該例直接轉(zhuǎn)換成泛型,那么會(huì)在編譯時(shí)失敗,因?yàn)橘x值是不被允許的:

          List<Integer> iList = new ArrayList<Integer>();
          List<Number> nList = iList; // not allowed
          nList.add(0.5);

            如果使用泛型,只要代碼在編譯時(shí)沒有出現(xiàn)警告,就不會(huì)遇到運(yùn)行時(shí) ClassCastException

          上限通配符

            我們想要的是一個(gè)確切元素類型未知的列表,這一點(diǎn)與數(shù)組是不同的。

          List<Number> 是一個(gè)列表,其元素類型是具體類型 Number
          List<? extends Number>
          是一個(gè)確切元素類型未知的列表。它是 Number 或其子類型。

          上限

            如果我們更新初始的例子,并賦值給 List<? extends Number> ,那么現(xiàn)在賦值就會(huì)成功了:

          List<Integer> iList = new ArrayList<Integer>();
          List<? extends Number> nList = iList;
          Number n = nList.get(0);
          nList.add(0.5); // Not allowed

            我們可以從列表中得到 Number ,因?yàn)闊o論列表的確切元素類型是什么( Float Integer Number ),我們都可以把它賦值給 Number

            我們?nèi)匀徊荒馨迅↑c(diǎn)類型插入列表中。這會(huì)在編譯時(shí)失敗,因?yàn)槲覀儾荒茏C明這是安全的。如果我們想要向列表中添加浮點(diǎn)類型,它將破壞 iList 的初始類型安全 —— 它只存儲(chǔ) Integer

            通配符給了我們比數(shù)組更多的表達(dá)能力。

          為什么使用通配符

            在下面這個(gè)例子中,通配符用于向 API 的用戶隱藏類型信息。在內(nèi)部, Set 被存儲(chǔ)為 CustomerImpl 。而 API 的用戶只知道他們正在獲取一個(gè) Set ,從中可以讀取 Customer
          此處通配符是必需的,因?yàn)闊o法從 Set<CustomerImpl> Set<Customer> 賦值:

          public class CustomerFactory {
          ??? private Set<CustomerImpl> _customers;
          ??? public Set<? extends Customer> getCustomers() {
          ??????? return _customers;
          ??? }
          }

          通配符和協(xié)變返回

            通配符的另一種常見用法是和協(xié)變返回一起使用。與賦值相同的規(guī)則可以應(yīng)用到協(xié)變返回上。如果希望在重寫的方法中返回一個(gè)更具體的泛型類型,聲明的方法必須使用通配符:
          public interface NumberGenerator {
          ??? public List<? extends Number> generate();
          }
          public class FibonacciGenerator extends NumberGenerator {
          ??? public List<Integer> generate() {
          ??????? ...
          ??? }
          }

            如果要使用數(shù)組,接口可以返回 Number[] ,而實(shí)現(xiàn)可以返回 Integer[]

          下限

            我們所談的主要是關(guān)于上限通配符的。還有一個(gè)下限通配符。 List<? super Number> 是一個(gè)確切 元素類型 未知的列表,但是可能是 Mnumber ,或者 Number 的超類型。所以它可能是一個(gè) List<Number> 或一個(gè) List<Object>

            下限通配符遠(yuǎn)沒有上限通配符那樣常見,但是當(dāng)需要它們的時(shí)候,它們就是必需的。

          下限與上限

          List<? extends Number> readList = new ArrayList<Integer>();
          Number n = readList.get(0);

          List<? super Number> writeList = new ArrayList<Object>();
          writeList.add(new Integer(5));

            第一個(gè)是可以從中讀數(shù)的列表。
            第二個(gè)是可以向其寫數(shù)的列表。

          無界通配符

            最后, List<?> 列表的內(nèi)容可以是任何類型,而且它與 List<? extends Object> 幾乎相同。可以隨時(shí)讀取 Object ,但是不能向列表中寫入內(nèi)容。
          公共 API 中的通配符

            總之,正如前面所說,通配符在向調(diào)用程序隱藏實(shí)現(xiàn)細(xì)節(jié)方面是非常重要的,但即使下限通配符看起來是提供只讀訪問,由于 remove(int position) 之類的非泛型方法,它們也并非如此。如果您想要一個(gè)真正不變的集合,可以使用 java.util.Collection 上的方法,比如 unmodifiableList()

            編寫 API 的時(shí)候要記得通配符。通常,在傳遞泛型類型時(shí),應(yīng)該嘗試使用通配符。它使更多的調(diào)用程序可以訪問 API

            通過接收 List<? extends Number> 而不是 List<Number> ,下面的方法可以由許多不同類型的列表調(diào)用:

          void removeNegatives(List<? extends Number> list);

          構(gòu)造泛型類型

            現(xiàn)在我們將討論構(gòu)造自己的泛型類型。我們將展示一些例子,其中通過使用泛型可以提高類型安全性,我們還將討論一些實(shí)現(xiàn)泛型類型時(shí)的常見問題。
          集合風(fēng)格 (Collection-like) 的函數(shù)

            第一個(gè)泛型類的例子是一個(gè)集合風(fēng)格的例子。 Pair 有兩個(gè)類型參數(shù),而且字段是類型的實(shí)例:

          public final class Pair<A,B> {
          ??? public final A first;
          ??? public final B second;

          ??? public Pair(A first, B second) {
          ??????? this.first = first;
          ??????? this.second = second;
          ??? }
          }

            這使從方法返回兩個(gè)項(xiàng)而無需為每個(gè)兩種類型的組合編寫專用的類成為可能。另一種方法是返回 Object[] ,而這樣是類型不安全或者不整潔的。

          在下面的用法中,我們從方法返回一個(gè) File 和一個(gè) Boolean 。方法的客戶端可以直接使用字段而無需類型強(qiáng)制轉(zhuǎn)換:

          public Pair<File,Boolean> getFileAndWriteStatus(String path){
          ??? // create file and status
          ??? return new Pair<File,Boolean>(file, status);
          }

          Pair<File,Boolean> result = getFileAndWriteStatus("...");
          File f = result.first;
          boolean writeable = result.second;

          集合之外

            在下面這個(gè)例子中,泛型被用于附加的編譯時(shí)安全性。通過把 DBFactory 類參數(shù)化為所創(chuàng)建的 Peer 類型,您實(shí)際上是在強(qiáng)制 Factory 子類返回一個(gè) Peer 的特定子類型:

          public abstract class DBFactory<T extends DBPeer> {
          ??? protected abstract T createEmptyPeer();
          ??? public List<T> get(String constraint) {
          ??????? List<T> peers = new ArrayList<T>();
          ??????? // database magic
          ??????? return peers;
          ??? }
          }
          通過實(shí)現(xiàn) DBFactory<Customer> CustomerFactory 必須從 createEmptyPeer() 返回一個(gè) Customer
          public class CustomerFactory extends DBFactory<Customer>{

          ??? public Customer createEmptyPeer() {
          ??????? return new Customer();
          ??? }
          }

          泛型方法

            不管想要對(duì)參數(shù)之間還是參數(shù)與返回類型之間的泛型類型施加約束,都可以使用泛型方法:

            例如,如果編寫的反轉(zhuǎn)函數(shù)是在位置上反轉(zhuǎn),那么可能不需要泛型方法。然而,如果希望反轉(zhuǎn)返回一個(gè)新的 List ,那么可能會(huì)希望新 List 的元素類型與傳入的 List 的類型相同。在這種情況下,就需要一個(gè)泛型方法:

          <T> List<T> reverse(List<T> list)

          具體化

            當(dāng)實(shí)現(xiàn)一個(gè)泛型類時(shí),您可能想要構(gòu)造一個(gè)數(shù)組 T[] 。因?yàn)榉盒褪峭ㄟ^擦除 (erasure) 實(shí)現(xiàn)的,所以這是不允許的。

            您可以嘗試把 Object[] 強(qiáng)制轉(zhuǎn)換為 T[] 。但這是不安全的。

          具體化解決方案

            按照泛型教程的慣例,解決方案使用的是 類型令牌 ,通過向構(gòu)造函數(shù)添加一個(gè) Class<T> 參數(shù),可以強(qiáng)制客戶端為類的類型參數(shù)提供正確的類對(duì)象:

          public class ArrayExample<T> {
          ??? private Class<T> clazz;

          ??? public ArrayExample(Class<T> clazz) {
          ??????? this.clazz = clazz;
          ??? }

          ??? public T[] getArray(int size) {
          ??????? return (T[])Array.newInstance(clazz, size);
          ??? }
          }

            為了構(gòu)造 ArrayExample<String> ,客戶端必須把 String.class 傳遞給構(gòu)造函數(shù),因?yàn)?/font> String.class 的類型是 Class<String>
          擁有類對(duì)象使構(gòu)造一個(gè)具有正確元素類型的數(shù)組成為可能。

          結(jié)束語

            總而言之,新的語言特性有助于從根本上改變 Java 。通過了解在什么場(chǎng)景下使用以及如何使用這些新特性,您將會(huì)編寫出更好的代碼。



          凡是有該標(biāo)志的文章,都是該blog博主Caoer(草兒)原創(chuàng),凡是索引、收藏
          、轉(zhuǎn)載請(qǐng)注明來處和原文作者。非常感謝。

          posted on 2006-06-11 17:31 草兒 閱讀(263) 評(píng)論(0)  編輯  收藏 所屬分類: java
          主站蜘蛛池模板: 阳新县| 常熟市| 东光县| 许昌市| 姜堰市| 称多县| 平利县| 蒙自县| 颍上县| 鲁甸县| 翁牛特旗| 宁都县| 溆浦县| 来宾市| 乃东县| 邵阳市| 遂平县| 汉川市| 衡阳县| 周口市| 蛟河市| 南京市| 兰西县| 饶阳县| 南澳县| 大丰市| 丰原市| 靖安县| 乌兰县| 金溪县| 洛浦县| 卢湾区| 天全县| 香河县| 临武县| 平昌县| 靖边县| 巍山| 霍林郭勒市| 丹棱县| 彭山县|