戲說Java泛型
Sun在Java 5之后的版本中引入了泛型技術(Generic).泛型是指具有一個或多個類型參數的類或者是接口.泛型技術其實在C++的STL中早已廣泛使用,在Java中泛型主要是用來構建安全的集合,我們在使用JCF(Java Collections Framework)時經常碰到它們的身影.當然泛型還有很多用處,它可以大大提高程序的可復用性。但是至少有90%的Java程序員都只在構建集合時使用這種技術.在Java中引入泛型技術之前,我們使用集合時,可以向其中加入任何對象,這就造成了很多不安全的因素,例如:
//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(); //獲取迭代器對象
int sum=0;
while(i.hasNext()){
Integer num=(Integer)i.next(); //將集合中得到的對象強制轉換為Integer,Note:這里最容易出現問題!
sum+=num.intValue(); /*進行拆箱操作,當然在Java5之后的版本這個動作可以自動完成,我們這里模擬的是Java1.4,研究泛型我們需要復古~
} *這是一門新的科學,叫做代碼考古學^_^ */
return sum;
}
這個程序會編譯成功!并且不會出現警告!但是在運行時會拋出ClassCastException這種運行時異常,String不能通過Integer的instanceof測試,并且我們從集合中取出元素時,因為返回的類型都是Object,我們必須對其進行強制轉換,嗯~這個操作是我最討厭的——不但麻煩,多敲了好幾下鍵盤,而且非常不安全,我不敢確定這個Object是我所希望轉換成的類型,如果真要做這種操作,希望大家都先進行一下instanceof測試,安全第一,受罪第二,不過現在確保安全是為了以后受更少的罪!將來在維護程序時,你甚至可能會因為這個小小的原因而一怒之下產生想重寫整個程序的沖動!嗯~實不相瞞,我就這樣做過~血的教訓!
但是有了泛型一切都好了起來,我們可以告訴編譯器每個集合中接受哪些對象類型,編譯器會自動為你做轉換工作,這樣以來我們在編譯時就知道是否向集合中插入了類型錯誤的元素.
List<Integer> intList=new ArrayList<Integer>();
現在intList這個集合就只能夠接受Integer類型的對象:
intList.add(new Integer(12));
如果我們向其中加入一個非Integer的對象,那么編譯器將報錯!
考慮這種情況:
List<E> list=new ArrayList<E>;
如果F是E的子類型,list中能添加F類型的對象嗎?當然可以!同數組一樣,子類都可以加入到父類型的集合中,另外對于接口也是如此,比如Bird類和Plane類都實現了一個Flyable的接口,我們想要在一個集合中放置所有"具有飛行能力"的對象,就可以很簡單的這樣做:
List<Flyable> flyerList=new ArrayList<Flyable>();
flyerList.add(new Bird());
flyerList.add(new Plane());
這樣很和諧,不是嗎?像List<Object>這種形式的集合當然就可以容納天下所有類型的對象了!
您可能會疑問,List<Object>能夠容納所有的對象,那么它不就與原生態(我們將不帶任何泛型信息的類型成為原生態類型,原生態是一個很時髦的東西,但是在Java中我們要避免它)的List相同了嗎?也就是說:
List list=new ArrayList();
List<Object> objList=new ArayList<Object>();
上述這兩行代碼所產生的東西是完全一樣的嗎?其實它們是有差距的,我們通過一個小程序來驗證:
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){ //我們在這里用的是原生態集合參數
Iterator iterator=list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
程序運行的非常順利!它可以喊出集合中包含的所有名字,同時我們也看到,我們可以將List<String>傳遞給一個接收原生態List的方法.如果你擅長向編譯器"找茬"的話,可能會發現這個地方似乎有一個漏洞,然后你會嘗試寫出類似如下的代碼:
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));
}
我們將一個Integer對象插入到一個String類型的集合中.測試一下,程序能成功編譯,但是會產生警告,如果你是在命令行下使用的javac命令進行編譯,就可以看到這條警告:使用了未經檢查或不安全的操作.如果使用現代IDE,例如Eclipse,也會看到用使人很不舒服的黃色曲線標識著list.add(new Integer(10))這條語句.可見編譯器非常不希望我們這樣做!但是之所以能通過編譯,主要是為了保持移植兼容性,與過去的Java代碼保持兼容.
泛型最初加入到Java中并不受歡迎.Sun在Java中引入泛型技術最大的挑戰就是做到使具有類型安全的泛型類和原來的原生態類能夠協同工作,然而這樣就會如上述代碼所示,引起許多棘手的代碼安全問題,編譯器就只能就這個問題發出警告,提醒程序員最好不要這樣做.這些安全隱患我們總是在程序運行時才能發現,JVM對于泛型這種東西毫無概念!泛型概念對于編譯器而言是嚴格的,編譯器在編譯具有泛型信息的代碼時,會對泛型類型進行驗證,然后執行一個類型擦除過程,也就是從類字節碼中去掉這些信息!當JVM在運行時就看不到所謂的泛型了,這個就是編譯器的"泛型陰謀",我們有必要了解這個事實.
下面我們將原生態的List換成List<Object>,情況會怎么樣呢?
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));
}
嗯~它會出錯!我們一再強調泛型是安全的,我們不能把一個List<String>傳遞給一個接受List<Object>參數的方法!如果您對Java數組比較熟悉,此時可能會產生疑問,您可能會經常碰見這樣的代碼:
Object[] objects=new Integer[]{1,2,3,4,5,6};
我們可以很正常的將一個子類型的數組賦給一個超類型的數組的引用,但是我們絕對不可以這樣做:
List<Object>objList=new ArrayList<String>();
編譯器是堅決不會讓你通過的!嗯~你感到泛型不夠人性化是不是?它要是能跟數組一樣就方便了~但是你看一看下面的程序,也許你就不會這么想了,相反,你或許還會認為Java中的數組存在缺陷:
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!問題出在這兒!
}
嗯~你是不是發現同上面的某個程序類似?它同樣可以通過編譯,但是我們在運行時發現它會產生一個叫做ArrayStoreException的異常!顧名思義,我們向這個數組當中放入了它所不能接受的東西.這一切的禍根都是源于Java允許我們可以將一個子類型的數組賦給一個超類型的數組的引用~然而使用泛型就不會發生這種情況,編譯器堅決阻止這種情況的存在!
數組同泛型似乎是水火不相容,數組是具體化的對象,只有在程序運行時才能搞清楚它們的類型,而對于泛型,我們前面說過,它僅僅在編譯的時候才會存在!基于這個原因我們不可以創建具有泛型信息的數組:例如new E[] new List<Object>[] 這些做法都是錯誤的!一定要注意!
然而有一種神奇的方法能夠將泛型為子類型的集合創遞給泛型為父類型的引用,我們在Windows下查找文件時經常會用到通配符,比如"*.jpg"表示所有JPG類型的文件,在Java中也有一套適用于泛型的通配符,下面是一個運用了泛型通配符的程序:
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");
}
}
我們在上述程序中簡單的構建了幾個游戲的角色類,抽象類Rpg及它的子類Knight(騎士)和Magician(術士),所有的游戲角色都會進行普通攻擊,然而攻擊的能力各不相同,騎士的攻擊強度比較大,而術士的普通攻擊相對要弱一些,他們主要依靠高科技的魔法攻擊~基于這個原則,我們在Rpg的子類中重寫了這個方法.現在我們要做的是讓一群騎士和術士進行戰斗.用doAttack()方法來號召他們戰斗.您或許早就注意了doAttack()的參數很奇怪:List<? extends Rpg>
這個就是前面我們所說的泛型通配符,<? extends Rpg>表示可以接受泛型類型是Rpg或Rpg子類型的集合,并且,很重要的一點,千萬不要向這些集合中添加任何元素,否則會引起編譯錯誤,原因很簡單,如果允許添加元素的話,很有可能將一個術士插入到一群騎士的隊伍當中!有一個例外,你可以向其中添加null元素
通配符?后的extends不僅代表著子類也代表的接口的實現,比如<? extends Serializable>表示所有實現泛型類型實現Serializable接口的集合.嗯~我沒寫錯,的確是extends,盡管這是一個接口,記住,沒有<? implements Serializable>這種形式,這就是語法,我們必須遵守
除了extends,另外還有一個泛型通配符關鍵字super,關于它的作用,請閱讀下面的程序:
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");
}
}
你一眼就會注意到,我們終于可以用親愛的add()方法來添加不是null的東西了!這都是super的功勞,List<? super Knight>的意思是凡是泛型類型為Knight和Knight超類的集合都能夠接受,在程序中你可以看到。我們可以將新的騎士加入到騎士的隊伍當中,或者是將騎士加入到混合角色的隊伍當中,不會發生將騎士插入到專業的術士隊伍當中這樣的錯誤,因為編譯器會阻止術士類型的集合傳遞到這個增加騎士的方法中
然而當我們什么關鍵字都不使用呢?就像這樣List<?> 你認為它等同于List<Object>嗎?那么你錯了,我們前面說過,為了安全,List<Object>只能接受泛型類型為Object類型的集合,而List<?>可以接受泛型類型為任何類型的集合!List<?>和List<? extends Object>是等同的,大家可以自己寫程序測試一下!可以繼續把我的騎士和術士的故事講下去~
使用通配符時要注意,通配符只是針對引用聲明使用,使用new生成對象時不可以使用通配符!
List<? extends Rpg> rpgs=new ArrayList<Knight>(); //這是正確的
List<? extends Rpg> magicians=new ArrayList<? super Magician>(); //這是錯誤的!
我們說過,應用泛型可以大大提高程序的可復用性,下面我們將學習如何創建我們自己的泛型類,比上面的要簡單,至少沒有那么多的編譯錯誤和異常,呵呵,可以把心情放松一下,下面的例子,我們構造一個使用泛型的鏈表節點:
class Node<T>{
private T value; //節點所包含的值
private Node<T> nextNode; //節點所指向的下一個節點
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就是泛型的標識符,然后在類中,我們可以像使用正常類一樣使用泛型標識符,在類定義中可以使用多個泛型標識符:
class Map<K,V>{
……
}
我們也可以使用通配符來指定泛型所允許的范圍:
class RpgHolder<T extends Rpg>{ //只允許RPG及其子類
……
}
有時候我們不需要使用一個泛型類,我們只需要在普通類中簡單的定義一個支持泛型的方法:
public <T extends Rpg> void makeRpgList(T t){
List<T> rpgList=new ArrayList<T>();
rpgList.add(t);
}
首先我們要聲明方法的泛型標識符<T extends Rpg>,然后像泛型類那樣在方法中使用泛型
Java 5出現之后,學習變得越來越困難,泛型技術是主要的困難因素之一,之所以困難主要是因為它要與以前的代碼保持兼容,這就大大的增加了復雜性,您也看到了,前面的那一大堆問題~但是學習泛型技術是很有用的,增加了代碼的可復用性,以及類型安全.本文主要闡述了一些簡單的理論,大家平時要多練習,有很多事可以做,比如可以嘗試用泛型去重新實現一些數據結構,優化一些常用的工具類,你會發現這是件非常有趣的事!
下面是我自己寫的一個類似List的集合——Tiny,當然比起JCF來在實際應用中性能不是很好,但是包含了基本的集合操作,習慣了JCF,很多數據結構的具體實現都忘得差不多了~得復習了~哈哈~閑著沒事練手~練手~這個是使用一個長度可變的數組來實現的,大家可以嘗試一下用鏈表來實現它的另一個版本LinkedTiny,這樣可以省去變化數組長度的麻煩~
/*-------------- 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(); //刪除集合的最后一個元素
void removeFirst(); //刪除集合中的第一個元素
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(); //獲取集合中當前元素數目
void clear(); //清除整個集合中的元素
boolean contain(T valueIn); //測試集合中是否包含值為valueIn的元素
Iterator<T> iterator(); //返回該集合的迭代器對象
}
/*-------------- ArrayTiny.java------*/
package sam.adt;
public class ArrayTiny<T> implements Tiny<T>{
private static final long serialVersionUID=19891107000000001L;
private final int INIT_SIZE=10; //默認初始化數組大小
private Object[] elements; //用來存儲數據的數組
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++;
}
}
//增加內部數組的長度
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;
}
}
//減小內部數組的長度
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;
}
}