4.泛型與數據類型轉換
4.1. 消除類型轉換
上面的例子大家看到什么了,數據類型轉換的代碼不見了。在以前我們經常要書寫以下代碼,如:
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable h = new Hashtable();
h.put("key", "value");
String s = (String)h.get("key");
System.out.println(s);
}
}
這個我們做了類型轉換,是不是感覺很煩的,并且強制類型轉換會帶來潛在的危險,系統可能會拋一個ClassCastException異常信息。在JDK5.0中我們完全可以這么做,如:
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable<String,Integer> h = new Hashtable<String,Integer> ();
h.put("key", new Integer(123));
int s = h.get("key").intValue();
System.out.println(s);
}
}
這里我們使用泛化版本的HashMap,這樣就不用我們來編寫類型轉換的代碼了,類型轉換的過程交給編譯器來處理,是不是很方便,而且很安全。上面是String映射到String,也可以將Integer映射為String,只要寫成HashTable<Integer,String> h=new HashTable<Integer,String>();h.get(new Integer(0))返回value。果然很方便。
4.2 自動解包裝與自動包裝的功能
從上面有沒有看到有點別扭啊,h.get(new Integer(123))這里的new Integer(123);好煩的,在JDK5.0之前我們只能忍著了,現在這種問題已經解決了,請看下面這個方法。我們傳入一個int這一基本型別,然后再將i的值直接添加到List中,其實List是不能儲存基本型別的,List中應該存儲對象,這里編譯器將int包裝成Integer,然后添加到List中去。接著我們用List.get(0);來檢索數據,并返回對象再將對象解包裝成int。恩,JDK5.0給我們帶來更多方便與安全。
public void autoBoxingUnboxing(int i) {
ArrayList<Integer> L= new ArrayList<Integer>();
L.add(i);
int a = L.get(0);
System.out.println("The value of i is " + a);
}
4.3 限制泛型中類型參數的范圍
也許你已經發現在TestGen<K,V>這個泛型類,其中K,V可以是任意的型別。也許你有時候呢想限定一下K和V當然范圍,怎么做呢?看看如下的代碼:
class TestGen2<K extents String,V extends Number>
{
private V v=null;
private K k=null;
public void setV(V v){
this.v=v;
}
public V getV(){
return this.v;
}
public void setK(K k){
this.k=k;
}
public V getK(){
return this.k;
}
public static void main(String[] args)
{
TestGen2<String,Integer> t2=new TestGen2<String,Integer>();
t2.setK(new String("String"));
t2.setV(new Integer(123));
System.out.println(t2.getK());
System.out.println(t2.getV());
}
}
上邊K的范圍是<=String ,V的范圍是<=Number,注意是“<=”,對于K可以是String的,V當然也可以是Number,也可以是Integer,Float,Double,Byte等??纯聪聢D也許能直觀些請看上圖A是上圖類中的基類,A1,A2分別是A的子類,A2有2個子類分別是A2_1,A2_2。
然后我們定義一個受限的泛型類class MyGen<E extends A2>,這個泛型的范圍就是上圖中蘭色部分。
這個是單一的限制,你也可以對型別多重限制,如下:
class C<T extends Comparable<? super T> & Serializable>
我們來分析以下這句,T extends Comparable這個是對上限的限制,Comparable< super T>這個是下限的限制,Serializable是第2個上限。一個指定的類型參數可以具有一個或多個上限。具有多重限制的類型參數可以用于訪問它的每個限制的方法和域。
5.泛型方法
考慮寫一個持有數組類型對象和一個集合對象的方法,把數組里的所有對象都放到
集合里。第一個程序為:
static void fromArrayToColleciton(Object[]a,Collection<?> c){
for (Object o : a){
c.add(o);//編譯時錯誤
}
}
到現在為止,你可能學會避免開始的錯誤而去使用Collection<Object>作為集合參數的類型,你可能會意識到使用Colleciton<?>將不會工作。
解決這個問題的方法是使用泛型方法,GENERIC METHODS,就像類型聲明、方法聲明一樣,就是被一個或更多的類型參數參數化。
static <T> void fromArrayToCollection(T[]a,Collection<T> c){
for(T o :a){
c.add(o);//正確
}
}
我們可以用任意類型的集合調用這個方法,他的元素類型是數組元素類型的超類型。
Object[] oa = new Object[100];
Collection <Object> co = new ArrayList<Object>();
fromArrayToCollection(oa,co);//T 被認為是Object類型
String[] sa = new String[100];
Colleciton<String> cs = new ArrayList<String>();
fromArrayToCollection(sa,cs);//T被認為是String類型
fromArrayToCollection(sa,co);//T 被認為是Object類型
Integer[] is = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(is,cn);//Number
fromArrayToCollection(fa,cn);//Number
fromArrayToCollection(na,cn);//Number
fromArrayToCollection(na,co);//Object
fromArrayToCollection(na,cs);//編譯時錯誤
我們不必給一個泛型方法傳遞一個真正的類型參數,編譯器會推斷類型參數.一個問題出現了,什么時候使用泛型方法,什么時候使通配符類型,為了回答這些問題,我們從Colleciton庫中看一下幾個方法:
interface Collection<E>{
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
使用泛型方法的形式為:
interface Collection<E>{
public <T> boolean containsAll(Collection<T> c);
public <T extends E> boolean addAll(Collection<T> c);
}
無論如何,在ContainAll和addAll中,類型參數T僅被使用一次。返回類型不依賴于類型參數,也不依賴方法中的任何參數。這告訴我類型參數正被用于多態,它的影響僅僅是允許不同的實參在不同的調用點被使用。
泛型方法允許類型參數被用于表達在一個或更多參數之間或者方法中的參數、返回類型的依賴。如果沒有如此依賴,泛型方法就不能被使用??赡芤磺耙缓髞砺摵鲜褂梅盒秃屯ㄅ浞?,這里有個例子:
class Collections{
public static <T> void copy(List<T> dest,List<? extends T> src){
}
}
注意兩個參數之間的依賴,任何從原list的對象復制,必須分配一個目標LIST元素的類型T,于是Src的元素類型可能是任何T的子類型。我們不必在意在COPY的表達中,表示依賴使用一個類型參數,但是是使用一個通配符。
下面我們不使用通配符來重寫上面的方法:
class Collections{
public static <T,S extends T>
void copy(List<T> dest,List<S> src){
}
}
這非常好,但是第一個類型參數既在dst中使用,也在第二個類型參數中使用,S本身就被使用了一次。在類型src中,沒有什么類型依賴它。這是一個標志我們可以用通配符來替換它。使用通配符比顯示的聲明類型參數更加清楚和精確。所以有可能推薦使用通配符。
通配符也有優勢,可以在方法之外來使用,作為字段類型、局部變量和數組。
這里有一個例子。
返回到我們畫圖的例子,假設我們要保持一個畫圖請求的歷史,我們可以在Shape類內部用一個靜態變量來保持歷史。用drawAll()存儲它到來的參數到歷史字段。
static List<List<? extends Shape>> history =
new ArrayList<List<? extends Shape>>();
public void drawAll(List<? extends Shape> shapes){
history.addLast(shapes);
for (Shape s : shapes){
s.draw(this);
}
}