管中窺虎
在學習
java 1.5
的過程中,我使用了
sun
公布的
tutorial
,這份文檔寫的比較詳盡易明,但是對于想快速了解
tiger
而且具有較好
java
基礎的人來說,大篇幅的英文文檔是比較耗時間和非必需的,所以我將會歸納這份文檔的主要內容,在保證理解的底線上,盡力減少閱讀者需要的時間。
?
在以下地址可以進入各新增語言特色介紹以及下載相關文檔(若有)。
http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html
?
第一道虎紋:
generic
-泛型
/
類屬
什么是泛型
泛型讓你在類這一層次上進行抽象。看看例子:
List?myIntList?
=
?
new
?LinkedList();?
//
?1?
myIntList.add(
new
?Integer(
0
));?
//
?2?
Integer?x?
=
?(Integer)?myIntList.iterator().next();?
//
?3?
?
?
第
3
句的轉換類型有點麻煩吧~?編譯器只能保證容器類里放的是
Object
對象,要使用他們只能這樣去轉換。而且這樣的轉換也并不是完全安全的,程序員可能犯錯誤,容器里的對象未必是他以為的對象。有沒有辦法顯式地表達出程序員的意圖,將該容器限制為只能保存特定類型的對象?這正是
generic
-泛型的核心用意。
?
?
List?
<
?Integer?
>
?myIntList?
=
?
new
?LinkedList?
<
?Integer?
>
?();?
//
?1’?
myIntList.add(
new
?Integer(
0
));?
//
2’?
Integer?x?
=
?myIntList.iterator().next();?
//
?3’?
?
?
這樣子我們就聲明了一個只放
Integer
的
List
,我們說
List
是一個
generic Interface
,接收了一個類型參數,在上例中就是
Integer
。在初始化的時候,同樣的也指定了這個類型參數。
要注意這些工作的效果不是僅僅把原來的第
3
句的轉換工作省掉,而是由此讓編譯器確保了這個
List
在程序的任何位置任何時候都用以存放正確的類型,而原來的類型轉換僅僅告訴我們在這一單點處程序員自己認為的類型。
泛型由此為程序,尤其是大型程序,帶來了可讀性和健壯性。
定義簡單的泛型
?
尖括號內的標識符就是一個類型形式參數。類似于方法的參數,當你使用的時候就替換一個實際參數進去,只不過這個參數是個類型。在上面的例子中,我們就替換了一個
Integer
類型進去。
在這里稍微說一下命名的規范,定義泛型中的形式參數時,使用簡潔有力又具有啟發性的名字,如果可以的話用單個字母更好。避免使用小寫,以免和普通的方法參數混淆。
?
泛型與子類
?
看看以下的例子語句合法嗎?
?
List?
<
?String?
>
?ls?
=
?
new
?ArrayList?
<
?String?
>
?();?
//
1?
List?
<
?Object?
>
?lo?
=
?ls;?
//
2?
?
第
2
句是行不通的,看看以下的語句:
lo.add(
new
?Object());?
//
?3?
String?s?
=
?ls.get(
0
);?
//
?4:?試圖將?Object?對象賦值給字符串對象,編譯錯誤?
簡而言之就是,原有類型的繼承關系是不會反映到對應的泛型上來,在上述情況下,任何兩個泛型類型都不存在繼承關系。那么,習慣了面向接口編程的我們怎樣去適應這種嚴格的使用限制呢?
?
通配符
假如我們要用一個方法把一個容器內的元素都
print
出來,可以用這樣的代碼來實現
:
void
?printCollection(Collection?c)?
{?

?????????Iterator?i?
=
?c.iterator();?


????for?(k?=?0;?k?<?c.size();?k++)?
{?

?????????System.out.println(i.next());?

??????}
?????}
?
如果我們用新的泛型和新的
for
語句(你可以先不了解它,以后會談到)來嘗試同樣的功能,以下代碼可行嗎?
?
事實是,新的代碼的使用范圍非常有限,因為
Collection
<
Object
>
就只是一個放
Object
的容器泛類,它不是
任何其他
Collection
泛類的父類!真正擔任這個角色的是:
Collection
<
?
>
這個問號代表了未知,
Collection
<
?
>
的元素可以是任何類型,這就是通配類型。
現在我們可以這樣寫:
void
?printCollection(Collection?
<
?
?
?
>
?c)?
{?


??
for
?(Object?e?:?c)?
{?

?????????System.out.println(e);?

??????}
?
}
?
?
注意在循環里面,可以把元素賦值給一個
Object
類型,因為無論
c
里放的是什么,它肯定是一個
Object
,但向
c
里放置對象則是不安全的,因為不知道
c
的泛型是什么。如下面這樣是不行的:
Collection?
<
?
?
?
>
?c?
=
?
new
?ArrayList?
<
?String?
>
?();?

c.add(
new
?Object());?
//
?編譯錯誤?
?
add()
方面接納的是泛型的形式參數里描述的類型(或它的子類,希望你對此不感到混亂,呵呵。),然而此時我們只看到一個問號,我們不知道它的類型參數是什么,當然就不能放置對象進去。唯一一個例外是
null
,它是任何一個類型的對象集的一分子。
?
受限通配符
省去一些說明性的代碼,以我們熟悉的幾何圖形家族例子來說明:
?
public
?
void
?drawAll(List?
<
?
?
?
extends
?Shape?
>
?shapes)?
{?
?}
?
?
一個
List<T>
,如果
T
是
Shape
的子類,那么這個
List
都可以被上面這個方法接納為參數,這個就是受限的通配符。
Shape
就稱為這個通配符的上限。
看看下面的代碼,怎樣?
public
?
void
?addRectangle(List?
<
?
?
?
extends
?Shape?
>
?shapes)?
{?

??????shapes.add(
0
,?
new
?Rectangle());?
//
?compile-time?error!?
}
?
如上的方法依然是不行的,因為我們只知道
shapes
是
Shape
或者其子類型的容器,然而具體類型是什么不知道,它未必是
Rectangle
的父類,所以不能放置元素進去。
?
總結起來,我們要了解的事情有:
l????????
泛型的用意
l????????
泛型的幾種形式(普通,通配符,受限通配符)
l????????
泛型與繼承關系一起使用時的易錯傾向,尤其是向泛型容器添加元素的情況。