Java基礎學習——泛型(generics)學習一

概述

在JDK 5.0,Java語言引入了好幾個新的功能,其中很重要的一個就是泛型(generics)。

本文就是對泛型的一個概述。你可以很熟悉其他語言中的類似結構,比如C++裏的模板(templates)。如果這樣,你將會看到兩者之間有相似,同時也有很大的不同。如果你之前並不熟悉酷似的東西,那就更好了,你將會沒有包袱,從而重頭開始。

泛型(generics)是一種對類型(types)的抽象。最常見的例子便是容量類型,比如 Java提供的Collections。

下面是Java中典型的排序用法。

List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3

第三行的強制類型轉換是比較煩人的。通常,程序員都是知道List裏是放的什麼類型的數據,然後,強制類型轉換卻是必不可少的。編譯器只能僅僅保證Object對象被迭代器返回(iterator)。爲了Integer變量賦值操作的類型安全,強制類型轉換是必須的。

當然,強制類型轉換並不僅僅帶來混亂,還有可能因爲程序員的失誤,帶來運行時的錯誤(run time error)。

能不能有種機制,程序員能明確表達意圖,使一個List僅僅只能包含一個指明的類型?那這便是泛型(generics)的核心思想。下面是上面那份代碼的泛型版本。

List<Integer> myIntList = new LinkedList<Integer>(); // 1'
myIntList.add(new Integer(0)); // 2'
Integer x = myIntList.iterator().next(); // 3'

留意下變量myIntList的聲明,它並不只是個List,而是一個被寫作List<Integer>的Integer List。我們叫List是一個泛型接口(generic interface),擁有一個type參數,Integer。當我們新建一個List對象的時候,需要特別指定一個type參數。同時,我們可以看到,第三行的強制類型轉換也消失了。

現在,你也行會認爲我們已經將雜亂的麻煩移除了。我們用第一行的類型參數Integer替換了第三行的Integer強制類型轉換。然而,這兩者卻又很大的不同。編譯器現在可以在編譯期進行類型安全檢測了。當我們用List<Integer>類型定義myIntList時,便告訴了編譯器一些約定,同時,編譯器會保證這些。在一些大型程序裏,泛型提供了更好的健壯性。

以上內容翻譯 Java 官網 Generics Introduction

簡單定義

下面是Jdk中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的類型參數。

類型參數會用在泛型聲明中所有需要真實類型地方(雖然有很多限制)。

在概述中,我們已經看到了泛型List的調用方法;在這個調用中,所有出現類型參數(通常也被叫做參數化的類型(parameterized type))的地方(在上面例子中的E)都會被真實類型替換(在上面例子中的Integer)。

你可能會想List<Integer>就是List的一個所有E都會Integer替換的特殊版本,像下面一樣:

public interface IntegerList {
    void add(Integer x);
    Iterator<Integer> iterator();
}

這種類比,雖然有些好處,但它同時也是一種誤導。

說這種類比有好處,是因爲參數化類型的List<Integer>的確看起來像是這種擴展。
說它誤導,是因爲泛型重沒有做這種替換擴展。泛型不是源代碼的副本,也不是二進制的,不是硬盤上副本,也不是內存裏的副本。如果你是個C++程序員,你就會發現這與C++模板非常不同。

一個泛型類型的源碼僅僅只會被編譯一次,被轉成一個Class文件,就像普通類和接口一樣。

類型參數在方法或構造函數裏的使用方式和普通參數一樣。就像函數參數聲明表示值參數(formal value parameters),描述的是運行的值一樣,一個泛型聲明表示一個類型參數(formal type parameters)。當一個方法被調用時,實際參數會取代形式參數,然後執行函數體。當一個泛型被調用時,這個實際類型會用來替換形式類型參數。

一些關於命名約定的注意事項。我們推薦使用簡短的方式(如果可以使用單字母)。最好能避免使用小寫字母,從而很方便和普通類和接口名稱區分。像上面的例子一樣,很多集合類型都用E代表元素。後面我們還會有更多的例子。

以上內容翻譯自Defining Simple Generics

泛型和子類型(Generics and Subtyping)

讓我們測試對泛型的理解。下面的代碼片段是否合法呢?

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

第一行,毫無疑問是合法的。棘手的問題是第二行。這個也就是說:一個String 的list是否是一個Object的List。大部分的人可能脫口而出說,是的。

那我們繼續看下面的這幾行:

lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!

上面的例子,我們將lo當成ls的別名。通過別名lo訪問了String的list ls,然後隨便插入了一個Object對象進去。結果ls就再也不能只保存String的對象了,當我們想從ls裏取出一些東西的時候,我們肯定會大吃一驚。

Java的編譯器會阻止上面的事情發生。上面的第二行會引起一個編譯期錯誤。
一般而言,如果Foo是Bar的一個子類型(subtype,(subclass or subinterface)),然後G是一個泛型定義,這不會導致G<Foo>是G<Bar>的一個子類型。這個也是泛型學習裏很困難的事情,因爲它違揹我們深信不疑的直覺。

我們不能假設容器都是不變的。我們的直覺總是讓我們感覺這些東西都是一成不變的。舉個栗子,如果交管局(the department of motor vehicles,DMV)擁有一個人口普查局(the census bureau)裏機動車駕駛員戶口信息的list,這其實挺合理的。我們可能會認爲List<Driver> 是一個List<Person>(is-a),假設Driver是Person的子類。但事實,只是把駕駛員信息做了一份拷貝。否則,如果人口普查局(the census bureau)新增一個非駕駛員的人口信息,將會污染DMV裏的記錄。

爲了應對這種情況,我們需要更靈活地看待泛型的類型。到目前爲止,我們看到的規則都是相當嚴格的。

以上內容翻譯自 Java 官網 Generics and Subtyping

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章