天天看點

Java1.5泛型總結一、泛型的定義二、泛型和子類繼承三、通配符四、泛型方法

一、泛型的定義

下面是從java.util包中的List接口和Iterator接口的定義中摘錄的片斷:

public interface List<E> {

           void add(E x);

           Iterator<E> iterator();

}

public interface Iterator<E> {

           E next();

           boolean hasNext();

}

這些都應該是很熟悉的,除了尖括号中的部分,那是接口List和Iterator中的形式類型參數的聲明(the declarations of the formal type parameters of the interfaces List and Iterator)。

類型參數在整個類的聲明中可用,幾乎是所有可是使用其他普通類型的地方(但是有些重要的限制,請參考第7部分)。

(原文:Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types (though there are some important restrictions; see section 7))

在介紹那一節我們看到了對泛型類型聲明List(the generic type declaration List)的調用,如List<Integer>。在這個調用中(通常稱作一個參數化類型a parameterized type),所有出現形式類型參數(formal type parameter,這裡是E)都被替換成實體類型參數(actual type argument)(這裡是Integer)。

你可能想象,List<Integer>代表一個E被全部替換成Integer的版本:

public interface IntegerList {

void add(Integer x)

Iterator<Integer> iterator();

}

這種直覺可能有幫助,但是也可能導緻誤解。

它有幫助,因為List<Integer>的聲明确實有類似這種替換的方法。

它可能導緻誤解,因為泛型聲明絕不會實際的被這樣替換。沒有代碼的多個拷貝,源碼中沒有、二進制代碼中也沒有;磁盤中沒有,記憶體中也沒有。如果你是一個C++程式員,你會了解這是和C++模闆的很大的差別。

一個泛型類型的聲明隻被編譯一次,并且得到一個class檔案,就像普通的class或者interface的聲明一樣。

類型參數就跟在方法或構造函數中普通的參數一樣。就像一個方法有形式參數(formal value parameters)來描述它操作的參數的種類一樣,一個泛型聲明也有形式類型參數(formal type parameters)。當一個方法被調用,實參(actual arguments)替換形參,方法體被執行。當一個泛型聲明被調用,實際類型參數(actual type arguments)取代形式類型參數。

二、泛型和子類繼承

讓我們測試一下我們對泛型的了解。下面的代碼片斷合法麼?

List<String> ls = new ArrayList<String>(); //1

List<Object> lo = ls; //2

第1行當然合法,但是這個問題的狡猾之處在于第2行。

這産生一個問題:

一個String的List是一個Object的List麼?大多數人的直覺是回答:“當然!”。

好,在看下面的幾行:

lo.add(new Object()); // 3

String s = ls.get(0); // 4: 試圖把Object指派給String

這裡,我們使用lo指向ls。我們通過lo來通路ls,一個String的list。我們可以插入任意對象進去。結果是ls中儲存的不再是String。當我們試圖從中取出元素的時候,會得到意外的結果。

java編譯器當然會阻止這種情況的發生。第2行會導緻一個編譯錯誤。

總之,如果Foo是Bar的一個子類型(子類或者子接口),而G是某種泛型聲明,那麼G<Foo>是G<Bar>的子類型并不成立!!

三、通配符

慮寫一個例程來列印一個集合(Collection)中的所有元素。下面是在老的語言中你可能寫的代碼:

            void printCollection(Collection c) {

                 Iterator i = c.iterator();

                 for (int 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作為參數,而新版本則隻能使用Collection<Object>,我們剛才闡述了,它不是所有類型的collections的父類。

那麼什麼是各種collections的父類呢?它寫作: Collection<?>(發音為:"collection of unknown"),就是,一個集合,它的元素類型可以比對任何類型。顯然,它被稱為通配符。我們可以寫:

void printCollection(Collection<?> c) {

for (Object e : c) {

System.out.println(e);

}

}

現在,我們可以使用任何類型的collection來調用它。注意,我們仍然可以讀取c中的元素,其類型是Object。這永遠是安全的,因為不管collection的真實類型是什麼,它包含的都是objects。但是将任意元素加入到其中不是類型安全的:

Collection<?> c = new ArrayList<String>();

c.add(new Object()); // 編譯時錯誤

因為我們不知道c的元素類型,我們不能向其中添加對象。

add方法有類型參數E作為集合的元素類型。我們傳給add的任何參數都必須是一個未知類型的子類。因為我們不知道那是什麼類型,是以我們無法傳任何東西進去。唯一的例外是null,它是所有類型的成員。

另一方面,我們可以調用get()方法并使用其傳回值。傳回值是一個未知的類型,但是我們知道,它總是一個Object,是以把get的傳回值指派給一個Object類型的對象或者放在任何希望是Object類型的地方是安全的。

3.1 有限制的通配符

我們有一個shape類,他有一個畫圖方法Draw(),有多個子類矩形、圓形等

所有的圖形通常都有很多個形狀。假定它們用一個list來表示,Canvas裡有一個方法來畫出所有的形狀會比較友善:

      public void drawAll(List<Shape> shapes) {

          for (Shape s : shapes) {

             s.draw(this);

         }

現在,類型規則導緻drawAll()隻能使用Shape的list來調用。它不能,比如說對List<Circle>來調用。這很不幸,因為這個方法所作的隻是從這個list讀取shape,是以它應該也能對List<Circle>調用。我們真正要的是這個方法能夠接受一個任意種類的shape:

public void drawAll(List<? extends Shape> shapes) { //..}

像平常一樣,要得到使用通配符的靈活性有些代價。這個代價是,現在像shapes中寫入是非法的。比如下面的代碼是不允許的:

         public void addRectangle(List<? extends Shape> shapes) {

       shapes.add(0, new Rectangle()); // compile-time error!

    }

你應該能夠指出為什麼上面的代碼是不允許的。因為shapes.add的第二個參數類型是? extends Shape ——一個Shape未知的子類。是以我們不知道這個類型是什麼,我們不知道它是不是Rectangle的父類;它可能是也可能不是一個父類,是以這裡傳遞一個Rectangle不安全

四、泛型方法

參數多态

static <T> void fromArrayToCollection(T[] a, Collection<T> c){

       for (T o : a) {

           c.add(o); // correct

       }

    }

注意,我們并沒有傳送真實類型參數(actual type argument)給一個泛型方法。編譯器根據實參為我們推斷類型參數的值。它通常推斷出能使調用類型正确的最明确的類型參數(原文是:It will generally infer the most specific type argument that will make the call type-correct.)。

現在有一個問題:我們應該什麼時候使用泛型方法,又什麼時候使用通配符類型呢?

為了了解答案,讓我們先看看Collection庫中的幾個方法。

public interface Collection<E> {

    boolean containsAll(Collection<?> c);

    boolean addAll(Collection<? extends E> c);

}

我們也可以使用泛型方法來代替:

public interface Collection<E> {

        <T> boolean containsAll(Collection<T> c);

        <T extends E> boolean addAll(Collection<T> c);

        //  hey, type variables can have bounds too!

}

但是,在 containsAll 和 addAll中,類型參數T 都隻使用一次。傳回值的類型既不依賴于類型參數(type parameter)也不依賴于方法的其他參數(這裡,隻有簡單的一個參數)。這告訴我們類型參數(type argument)被用作多态(polymorphism),它唯一的效果是允許在不同的調用點,可以使用多種實參類型(actual argument)。如果是這種情況,應該使用通配符。通配符就是被設計用來支援靈活的子類化的,這是我們在這裡要強調的。

泛型函數允許類型參數被用來表示方法的一個或多個參數之間的依賴關系,或者參數與其傳回值的依賴關系。如果沒有這樣的依賴關系,不應該使用泛型方法。

詳細見 點選打開連結