戲說Java泛型
Sun在Java 5之后的版本中引入了泛型技術(shù)(Generic).泛型是指具有一個(gè)或多個(gè)類型參數(shù)的類或者是接口.泛型技術(shù)其實(shí)在C++的STL中早已廣泛使用,在Java中泛型主要是用來構(gòu)建安全的集合,我們?cè)谑褂肑CF(Java Collections Framework)時(shí)經(jīng)常碰到它們的身影.當(dāng)然泛型還有很多用處,它可以大大提高程序的可復(fù)用性。但是至少有90%的Java程序員都只在構(gòu)建集合時(shí)使用這種技術(shù).在Java中引入泛型技術(shù)之前,我們使用集合時(shí),可以向其中加入任何對(duì)象,這就造成了很多不安全的因素,例如:
//in the main()
……
List intList=new ArrayList();
intList.add(new Integer(10));
intList.add(new Integer(20));
intList.add(new Integer(30));
intList.add(new String("Sam"));
int sum=getSum(intList);
……
public static int getSum(List intListIn){
Iterator i=intList.iterator(); //獲取迭代器對(duì)象
int sum=0;
while(i.hasNext()){
Integer num=(Integer)i.next(); //將集合中得到的對(duì)象強(qiáng)制轉(zhuǎn)換為Integer,Note:這里最容易出現(xiàn)問題!
sum+=num.intValue(); /*進(jìn)行拆箱操作,當(dāng)然在Java5之后的版本這個(gè)動(dòng)作可以自動(dòng)完成,我們這里模擬的是Java1.4,研究泛型我們需要復(fù)古~
} *這是一門新的科學(xué),叫做代碼考古學(xué)^_^ */
return sum;
}
這個(gè)程序會(huì)編譯成功!并且不會(huì)出現(xiàn)警告!但是在運(yùn)行時(shí)會(huì)拋出ClassCastException這種運(yùn)行時(shí)異常,String不能通過Integer的instanceof測(cè)試,并且我們從集合中取出元素時(shí),因?yàn)榉祷氐念愋投际荗bject,我們必須對(duì)其進(jìn)行強(qiáng)制轉(zhuǎn)換,嗯~這個(gè)操作是我最討厭的——不但麻煩,多敲了好幾下鍵盤,而且非常不安全,我不敢確定這個(gè)Object是我所希望轉(zhuǎn)換成的類型,如果真要做這種操作,希望大家都先進(jìn)行一下instanceof測(cè)試,安全第一,受罪第二,不過現(xiàn)在確保安全是為了以后受更少的罪!將來在維護(hù)程序時(shí),你甚至可能會(huì)因?yàn)檫@個(gè)小小的原因而一怒之下產(chǎn)生想重寫整個(gè)程序的沖動(dòng)!嗯~實(shí)不相瞞,我就這樣做過~血的教訓(xùn)!
但是有了泛型一切都好了起來,我們可以告訴編譯器每個(gè)集合中接受哪些對(duì)象類型,編譯器會(huì)自動(dòng)為你做轉(zhuǎn)換工作,這樣以來我們?cè)诰幾g時(shí)就知道是否向集合中插入了類型錯(cuò)誤的元素.
List<Integer> intList=new ArrayList<Integer>();
現(xiàn)在intList這個(gè)集合就只能夠接受Integer類型的對(duì)象:
intList.add(new Integer(12));
如果我們向其中加入一個(gè)非Integer的對(duì)象,那么編譯器將報(bào)錯(cuò)!
考慮這種情況:
List<E> list=new ArrayList<E>;
如果F是E的子類型,list中能添加F類型的對(duì)象嗎?當(dāng)然可以!同數(shù)組一樣,子類都可以加入到父類型的集合中,另外對(duì)于接口也是如此,比如Bird類和Plane類都實(shí)現(xiàn)了一個(gè)Flyable的接口,我們想要在一個(gè)集合中放置所有"具有飛行能力"的對(duì)象,就可以很簡單的這樣做:
List<Flyable> flyerList=new ArrayList<Flyable>();
flyerList.add(new Bird());
flyerList.add(new Plane());
這樣很和諧,不是嗎?像List<Object>這種形式的集合當(dāng)然就可以容納天下所有類型的對(duì)象了!
您可能會(huì)疑問,List<Object>能夠容納所有的對(duì)象,那么它不就與原生態(tài)(我們將不帶任何泛型信息的類型成為原生態(tài)類型,原生態(tài)是一個(gè)很時(shí)髦的東西,但是在Java中我們要避免它)的List相同了嗎?也就是說:
List list=new ArrayList();
List<Object> objList=new ArayList<Object>();
上述這兩行代碼所產(chǎn)生的東西是完全一樣的嗎?其實(shí)它們是有差距的,我們通過一個(gè)小程序來驗(yàn)證:
public static void main(String[] args){
List<String>names=new ArrayList<String>();
names.add("Sam");
names.add("Jack");
names.add("James");
names.add("Lucy");
sayAllNames(names);
}
private static void sayAllNames(List list){ //我們?cè)谶@里用的是原生態(tài)集合參數(shù)
Iterator iterator=list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
程序運(yùn)行的非常順利!它可以喊出集合中包含的所有名字,同時(shí)我們也看到,我們可以將List<String>傳遞給一個(gè)接收原生態(tài)List的方法.如果你擅長向編譯器"找茬"的話,可能會(huì)發(fā)現(xiàn)這個(gè)地方似乎有一個(gè)漏洞,然后你會(huì)嘗試寫出類似如下的代碼:
public static void main(String[] args){
List<String>names=new ArrayList<String>();
names.add("Sam");
names.add("Jack");
names.add("James");
names.add("Lucy");
addElement(names);
}
private static void addElement(List list){
list.add(new Integer(10));
}
我們將一個(gè)Integer對(duì)象插入到一個(gè)String類型的集合中.測(cè)試一下,程序能成功編譯,但是會(huì)產(chǎn)生警告,如果你是在命令行下使用的javac命令進(jìn)行編譯,就可以看到這條警告:使用了未經(jīng)檢查或不安全的操作.如果使用現(xiàn)代IDE,例如Eclipse,也會(huì)看到用使人很不舒服的黃色曲線標(biāo)識(shí)著list.add(new Integer(10))這條語句.可見編譯器非常不希望我們這樣做!但是之所以能通過編譯,主要是為了保持移植兼容性,與過去的Java代碼保持兼容.
泛型最初加入到Java中并不受歡迎.Sun在Java中引入泛型技術(shù)最大的挑戰(zhàn)就是做到使具有類型安全的泛型類和原來的原生態(tài)類能夠協(xié)同工作,然而這樣就會(huì)如上述代碼所示,引起許多棘手的代碼安全問題,編譯器就只能就這個(gè)問題發(fā)出警告,提醒程序員最好不要這樣做.這些安全隱患我們總是在程序運(yùn)行時(shí)才能發(fā)現(xiàn),JVM對(duì)于泛型這種東西毫無概念!泛型概念對(duì)于編譯器而言是嚴(yán)格的,編譯器在編譯具有泛型信息的代碼時(shí),會(huì)對(duì)泛型類型進(jìn)行驗(yàn)證,然后執(zhí)行一個(gè)類型擦除過程,也就是從類字節(jié)碼中去掉這些信息!當(dāng)JVM在運(yùn)行時(shí)就看不到所謂的泛型了,這個(gè)就是編譯器的"泛型陰謀",我們有必要了解這個(gè)事實(shí).
下面我們將原生態(tài)的List換成List<Object>,情況會(huì)怎么樣呢?
public static void main(String[] args){
List<String>names=new ArrayList<String>();
names.add("Sam");
names.add("Jack");
names.add("James");
names.add("Lucy");
addElement(names);
}
private static void addElement(List<Object> list){
list.add(new Integer(10));
}
嗯~它會(huì)出錯(cuò)!我們一再強(qiáng)調(diào)泛型是安全的,我們不能把一個(gè)List<String>傳遞給一個(gè)接受List<Object>參數(shù)的方法!如果您對(duì)Java數(shù)組比較熟悉,此時(shí)可能會(huì)產(chǎn)生疑問,您可能會(huì)經(jīng)常碰見這樣的代碼:
Object[] objects=new Integer[]{1,2,3,4,5,6};
我們可以很正常的將一個(gè)子類型的數(shù)組賦給一個(gè)超類型的數(shù)組的引用,但是我們絕對(duì)不可以這樣做:
List<Object>objList=new ArrayList<String>();
編譯器是堅(jiān)決不會(huì)讓你通過的!嗯~你感到泛型不夠人性化是不是?它要是能跟數(shù)組一樣就方便了~但是你看一看下面的程序,也許你就不會(huì)這么想了,相反,你或許還會(huì)認(rèn)為Java中的數(shù)組存在缺陷:
public static void main(String[] args){
Integer[] intArray=new Integer[]{1,2,3,4,5};
changeElement(intArray);
}
public static void changeElement(Object[] objects){
objects[0]=new String("I Love Java7"); //Hi!問題出在這兒!
}
嗯~你是不是發(fā)現(xiàn)同上面的某個(gè)程序類似?它同樣可以通過編譯,但是我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)它會(huì)產(chǎn)生一個(gè)叫做ArrayStoreException的異常!顧名思義,我們向這個(gè)數(shù)組當(dāng)中放入了它所不能接受的東西.這一切的禍根都是源于Java允許我們可以將一個(gè)子類型的數(shù)組賦給一個(gè)超類型的數(shù)組的引用~然而使用泛型就不會(huì)發(fā)生這種情況,編譯器堅(jiān)決阻止這種情況的存在!
數(shù)組同泛型似乎是水火不相容,數(shù)組是具體化的對(duì)象,只有在程序運(yùn)行時(shí)才能搞清楚它們的類型,而對(duì)于泛型,我們前面說過,它僅僅在編譯的時(shí)候才會(huì)存在!基于這個(gè)原因我們不可以創(chuàng)建具有泛型信息的數(shù)組:例如new E[] new List<Object>[] 這些做法都是錯(cuò)誤的!一定要注意!
然而有一種神奇的方法能夠?qū)⒎盒蜑樽宇愋偷募蟿?chuàng)遞給泛型為父類型的引用,我們?cè)赪indows下查找文件時(shí)經(jīng)常會(huì)用到通配符,比如"*.jpg"表示所有JPG類型的文件,在Java中也有一套適用于泛型的通配符,下面是一個(gè)運(yùn)用了泛型通配符的程序:
import java.util.*;
public class Test {
public static void main(String[] args){
List<Knight>knights=new ArrayList<Knight>();
knights.add(new Knight());
knights.add(new Knight());
knights.add(new Knight());
doAttack(knights);
}
public static void doAttack(List<? extends Rpg>rpgs){
Iterator<? extends Rpg>iterator=rpgs.iterator();
while(iterator.hasNext()){
iterator.next().commonAttack(); //這里next()方法返回的是Rpg
}
}
}
class abstract Rpg{
public abstract void commonAttack();
}
class Knight extends Rpg{
public void commonAttack(){
System.out.println("The common attack of the knight is very strong!");
}
}
class Magician extends Rpg{
public void commonAttack(){
System.out.println("The common attack of the magician is very weak");
}
}
我們?cè)谏鲜龀绦蛑泻唵蔚臉?gòu)建了幾個(gè)游戲的角色類,抽象類Rpg及它的子類Knight(騎士)和Magician(術(shù)士),所有的游戲角色都會(huì)進(jìn)行普通攻擊,然而攻擊的能力各不相同,騎士的攻擊強(qiáng)度比較大,而術(shù)士的普通攻擊相對(duì)要弱一些,他們主要依靠高科技的魔法攻擊~基于這個(gè)原則,我們?cè)赗pg的子類中重寫了這個(gè)方法.現(xiàn)在我們要做的是讓一群騎士和術(shù)士進(jìn)行戰(zhàn)斗.用doAttack()方法來號(hào)召他們戰(zhàn)斗.您或許早就注意了doAttack()的參數(shù)很奇怪:List<? extends Rpg>
這個(gè)就是前面我們所說的泛型通配符,<? extends Rpg>表示可以接受泛型類型是Rpg或Rpg子類型的集合,并且,很重要的一點(diǎn),千萬不要向這些集合中添加任何元素,否則會(huì)引起編譯錯(cuò)誤,原因很簡單,如果允許添加元素的話,很有可能將一個(gè)術(shù)士插入到一群騎士的隊(duì)伍當(dāng)中!有一個(gè)例外,你可以向其中添加null元素
通配符?后的extends不僅代表著子類也代表的接口的實(shí)現(xiàn),比如<? extends Serializable>表示所有實(shí)現(xiàn)泛型類型實(shí)現(xiàn)Serializable接口的集合.嗯~我沒寫錯(cuò),的確是extends,盡管這是一個(gè)接口,記住,沒有<? implements Serializable>這種形式,這就是語法,我們必須遵守
除了extends,另外還有一個(gè)泛型通配符關(guān)鍵字super,關(guān)于它的作用,請(qǐng)閱讀下面的程序:
import java.util.*;
public class Test {
public static void main(String[] args){
List<Knight>knights=new ArrayList<Knight>();
knights.add(new Knight());
knights.add(new Knight());
knights.add(new Knight());
List<Rpg>rpgs=new ArrayList<Rpg>();
addKnight(rpgs);
}
public static void addKnight(List<? super Knight>list){
list.add(new Knight());
}
}
class abstract Rpg{
public abstract void commonAttack();
}
class Knight extends Rpg{
public void commonAttack(){
System.out.println("The common attack of the knight is very strong!");
}
}
class Magician extends Rpg{
public void commonAttack(){
System.out.println("The common attack of the magician is very weak");
}
}
你一眼就會(huì)注意到,我們終于可以用親愛的add()方法來添加不是null的東西了!這都是super的功勞,List<? super Knight>的意思是凡是泛型類型為Knight和Knight超類的集合都能夠接受,在程序中你可以看到。我們可以將新的騎士加入到騎士的隊(duì)伍當(dāng)中,或者是將騎士加入到混合角色的隊(duì)伍當(dāng)中,不會(huì)發(fā)生將騎士插入到專業(yè)的術(shù)士隊(duì)伍當(dāng)中這樣的錯(cuò)誤,因?yàn)榫幾g器會(huì)阻止術(shù)士類型的集合傳遞到這個(gè)增加騎士的方法中
然而當(dāng)我們什么關(guān)鍵字都不使用呢?就像這樣List<?> 你認(rèn)為它等同于List<Object>嗎?那么你錯(cuò)了,我們前面說過,為了安全,List<Object>只能接受泛型類型為Object類型的集合,而List<?>可以接受泛型類型為任何類型的集合!List<?>和List<? extends Object>是等同的,大家可以自己寫程序測(cè)試一下!可以繼續(xù)把我的騎士和術(shù)士的故事講下去~
使用通配符時(shí)要注意,通配符只是針對(duì)引用聲明使用,使用new生成對(duì)象時(shí)不可以使用通配符!
List<? extends Rpg> rpgs=new ArrayList<Knight>(); //這是正確的
List<? extends Rpg> magicians=new ArrayList<? super Magician>(); //這是錯(cuò)誤的!
我們說過,應(yīng)用泛型可以大大提高程序的可復(fù)用性,下面我們將學(xué)習(xí)如何創(chuàng)建我們自己的泛型類,比上面的要簡單,至少?zèng)]有那么多的編譯錯(cuò)誤和異常,呵呵,可以把心情放松一下,下面的例子,我們構(gòu)造一個(gè)使用泛型的鏈表節(jié)點(diǎn):
class Node<T>{
private T value; //節(jié)點(diǎn)所包含的值
private Node<T> nextNode; //節(jié)點(diǎn)所指向的下一個(gè)節(jié)點(diǎn)
public Node(T valueIn){
value=valueIn;
nextNode=null;
}
public void setValue(T valueIn){
value=valueIn;
}
public T getValue(){
return value;
}
public void setNextNode(Node<T> nodeIn){
nextNode=nodeIn;
}
public Node<T> getNextNode(){
return nextNode;
}
}
如你所見,T就是泛型的標(biāo)識(shí)符,然后在類中,我們可以像使用正常類一樣使用泛型標(biāo)識(shí)符,在類定義中可以使用多個(gè)泛型標(biāo)識(shí)符:
class Map<K,V>{
……
}
我們也可以使用通配符來指定泛型所允許的范圍:
class RpgHolder<T extends Rpg>{ //只允許RPG及其子類
……
}
有時(shí)候我們不需要使用一個(gè)泛型類,我們只需要在普通類中簡單的定義一個(gè)支持泛型的方法:
public <T extends Rpg> void makeRpgList(T t){
List<T> rpgList=new ArrayList<T>();
rpgList.add(t);
}
首先我們要聲明方法的泛型標(biāo)識(shí)符<T extends Rpg>,然后像泛型類那樣在方法中使用泛型
Java 5出現(xiàn)之后,學(xué)習(xí)變得越來越困難,泛型技術(shù)是主要的困難因素之一,之所以困難主要是因?yàn)樗c以前的代碼保持兼容,這就大大的增加了復(fù)雜性,您也看到了,前面的那一大堆問題~但是學(xué)習(xí)泛型技術(shù)是很有用的,增加了代碼的可復(fù)用性,以及類型安全.本文主要闡述了一些簡單的理論,大家平時(shí)要多練習(xí),有很多事可以做,比如可以嘗試用泛型去重新實(shí)現(xiàn)一些數(shù)據(jù)結(jié)構(gòu),優(yōu)化一些常用的工具類,你會(huì)發(fā)現(xiàn)這是件非常有趣的事!
下面是我自己寫的一個(gè)類似List的集合——Tiny,當(dāng)然比起JCF來在實(shí)際應(yīng)用中性能不是很好,但是包含了基本的集合操作,習(xí)慣了JCF,很多數(shù)據(jù)結(jié)構(gòu)的具體實(shí)現(xiàn)都忘得差不多了~得復(fù)習(xí)了~哈哈~閑著沒事練手~練手~這個(gè)是使用一個(gè)長度可變的數(shù)組來實(shí)現(xiàn)的,大家可以嘗試一下用鏈表來實(shí)現(xiàn)它的另一個(gè)版本LinkedTiny,這樣可以省去變化數(shù)組長度的麻煩~
/*-------------- Iterator.java--------*/
package sam.adt;
public interface Iterator<T> {
boolean hasNext();
T next();
}
/*--------------- Tiny.java---------*/
package sam.adt;
import java.io.*;
public interface Tiny<T> extends Serializable{
void add(T valueIn); //將值添加到集合的尾部
void remove(); //刪除集合的最后一個(gè)元素
void removeFirst(); //刪除集合中的第一個(gè)元素
void addToHead(T valueIn); //將值添加到集合的頭部
boolean remove(T valueIn); //刪除值為valueIn的元素
boolean add(int indexIn,T valueIn); //在指定索引處添加值
boolean remove(int indexIn); //刪除指定索引處的元素
T get(int indexIn); //得到指定索引處的元素
int indexOf(T valueIn); //獲取指定值的索引
boolean replace(int indexIn,T valueIn); //將index處的值替換為value
int size(); //獲取集合中當(dāng)前元素?cái)?shù)目
void clear(); //清除整個(gè)集合中的元素
boolean contain(T valueIn); //測(cè)試集合中是否包含值為valueIn的元素
Iterator<T> iterator(); //返回該集合的迭代器對(duì)象
}
/*-------------- ArrayTiny.java------*/
package sam.adt;
public class ArrayTiny<T> implements Tiny<T>{
private static final long serialVersionUID=19891107000000001L;
private final int INIT_SIZE=10; //默認(rèn)初始化數(shù)組大小
private Object[] elements; //用來存儲(chǔ)數(shù)據(jù)的數(shù)組
private int size; //集合的邏輯大小
//適合ArrayTiny的迭代器
private class ArrayIterator implements Iterator<T>{
private int currentPos; //記錄迭代的位置索引
public ArrayIterator(){
currentPos=0;
}
public boolean hasNext() {
return currentPos<size;
}
public T next() {
T value=(T)elements[currentPos];
currentPos++;
return value;
}
}
public ArrayTiny(){
super();
elements=new Object[INIT_SIZE];
size=0;
}
public ArrayTiny(Iterator<T> iterator){
this();
while(iterator.hasNext()){
addArrayLength();
this.add(iterator.next());
size++;
}
}
//增加內(nèi)部數(shù)組的長度
private void addArrayLength(){
if(size==elements.length){
int newSize=size*2;
Object[] tempArray=new Object[newSize];
for(int i=0;i<size;i++){
tempArray[i]=elements[i];
}
elements=null;
System.gc();
elements=tempArray;
}
}
//減小內(nèi)部數(shù)組的長度
private void reduceArrayLength(){
if(size<elements.length/4&&size>INIT_SIZE){
int newSize=Math.max(size*2,INIT_SIZE);
Object[] tempArray=new Object[newSize];
for(int i=0;i<size;i++){
tempArray[i]=elements[i];
}
elements=null;
System.gc();
elements=tempArray;
}
}
public boolean add(int indexIn, T valueIn) {
if(indexIn>=0&&indexIn<size){
addArrayLength();
for(int i=size;i>indexIn;i--){
elements[i]=elements[i-1];
}
elements[indexIn]=valueIn;
size++;
}
return false;
}
public void add(T valueIn) {
addArrayLength();
elements[size]=valueIn;
size++;
}
public void addToHead(T valueIn) {
add(0,valueIn);
}
public void clear() {
elements=null;
elements=new Object[INIT_SIZE];
}
public boolean contain(T valueIn) {
int index=indexOf(valueIn);
if(index!=-1)return true;
return false;
}
public T get(int indexIn) {
if(indexIn>=0&&indexIn<size){
return (T)elements[indexIn];
}
return null;
}
public int indexOf(T valueIn){
for(int i=0;i<size;i++){
if(elements[i].equals(valueIn))return i;
}
return -1;
}
public Iterator<T> iterator() {
return new ArrayIterator();
}
public void remove() {
elements[size-1]=null;
size--;
reduceArrayLength();
}
public boolean remove(int indexIn) {
if(indexIn>=0&&indexIn<size){
for(int i=indexIn;i<size;i++){
elements[i]=elements[i+1];
}
size--;
reduceArrayLength();
return true;
}
return false;
}
public boolean remove(T valueIn) {
int index=indexOf(valueIn);
return remove(index);
}
public void removeFirst() {
remove(0);
}
public boolean replace(int indexIn, T valueIn) {
if(indexIn>=0&&indexIn<size){
elements[indexIn]=valueIn;
}
return false;
}
public int size() {
return size;
}
}