程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Jdk5.0新特性Generic Types(泛型)

Jdk5.0新特性Generic Types(泛型)

編輯:關於JAVA

1. 介紹

2.定義簡單Java泛型

其實Java的泛型就是創建一個用類型作為參數的類。就象我們寫類的方法一樣,方法是這樣的method(String str1,String str2 ),方法中參數str1、str2的值是可變的。而泛型也是一樣的,這樣寫class Java_Generics<K,V>,這裡邊的K和V就象方法中的參數str1和str2,也是可變。下面看看例子:

import java.util.Hashtable;
class TestGen0<K,V>{
  public Hashtable<K,V> h=new Hashtable<K,V>();
  public void put(K k, V v) {
   h.put(k,v);
  }
  public V get(K k) {
   return h.get(k);
  }
  public static void main(String args[]){
   TestGen0<String,String> t=new TestGen0<String,String>();
   t.put("key", "value");
   String s=t.get("key");
   System.out.println(s);
  }
}

正確輸出:value

這只是個例子,不過看看是不是創建一個用類型作為參數的類,參數是K,V,傳入的“值”是String類型。這個類他沒有特定的待處理型別,以前我們定義好了一個類,在輸入參數有所固定,是什麼型別的有要求,但是現在編寫程序,完全可以不制定參數的類型,具體用的時候來確定,增加了程序的通用性,像是一個模板。

3. 泛型通配符
首先,下面是一個例子,作用是打印出一個集合中的所有元素,我們首先用老版本jdk1.4的編碼規則,代碼如下:

void printColleciton(Collection c){

iterator i = c.iterator();

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

System.out.pritnln(i.next();

}

然後,我們用jdk5.0泛型來重寫上面這段代碼(循環的語法是新版本的語法):

void printCollection(Colleciton<Object> c){

for(Object e : c){

System.out.print(e);

}

}

這個新版本並不比老版本的好多少,老版本可以用任意一種集合類型作為參數來調用,而新版本僅僅持有Collection<Object>類型,Colleciton<Object>並不是任意類型的Collection的超類。

那麼什麼是所有Colleciton類型的超類型呢?它是Collection<?>這樣一個類型,讀作“未知Colleciton”。它的意思是說Colleciton的元素類型可以匹配任意類型,我們把它稱作通配符類型,我們這樣寫:

void printCollection(Colleciton<?> c){

for (Object e: c){

System.out.println(e);

}

}

現在我們用任意類型的集合來調用它了,需要注意的是內部方法printColleciton(),我們任可以從c中來讀出元素,並且這些元素是Object類型,而且是安全的,因為無論集合中是什麼類型,它總包括Object,但是將任意對象加到集合中是不安全的:

Colleciton<?> c = new ArrayList<String>();

c.add(new Object());//編譯時錯誤

由於我們不知道c持有的是什麼類型的元素,我們不能加object到集合中去。add()方法用類型E作為參數,(集合的元素類型)當真正的參數類型是?的時候,它代表的是一些未知類型。任何傳遞給add()方法的參數必須是這個未知類型的子類型。由於我們不知道未知類型,所以我們傳遞給它任何東西。主要的例外是null,它是每一個類型的成員。

另一方面,假定給一個List<?>,我們調用get()並且充分利用結果。結果類型是未知類型。但是我總是知道它是一個Object,因此分配一個從get()取出來的結果到一個object的變量是安全的,或者作為一個參數傳遞到一個需要object類型的地方。

3.1有限制的通配符

考慮一個畫圖的應用程序,這個程序能夠畫長方形、圓等類型,為了在程序中表示這樣的圖形,你可以定義一個類型的層次結構:

public abstract class Shape{ 

         public abstract void draw(Canvas c); 

 } 

public class Circle extends Shape{ 

         private int x,y,radius; 

         public void draw(Canvas c){} 

} 

public class Rectangle extends Shape{ 

         private int x,y,width,height; 

         public void draw(Canvas c){ 

} 

} 

//這些類能被畫在畫布上: 

public class Canvas{ 

       public void draw(Shape s){ 

                s.draw(this); 

       } 

}

任何畫圖的動作的都包含一些圖形,假設他們被表示在一個list中,在Canvas中它將會有一個很方便的方法來畫他們:

public void drawAll(List<Shape> shapes){

for(Shape s :shapes){

s.draw(this);

}

}

現在類型規則說,方法drawAll()只能在真正的Shape類型的List上被調用,而它的子類無法調用,例如List<Circle>上被調用。這是很不幸的。由於所有的方法確實從List中讀出Shape,所以它僅能在List<Object>上被調用,下面我們改後的代碼可以在任意類型的Shape上被調用:

public void drawAll(List< ? extends Shape>{ }

這裡有一個很小的不同是,我們已經用List<? extends Shape>替換了List<Object>,現在drawAll()方法可以接受任意的Shape的子類了,我們當然可以在List<Circle>上調用。

<? extends Class>是一種限制通配符類型,它可以接受所有<Class>以及Class的子類型。然而調用代價是,只讀訪問,無法向shapes中添加元素。像通常一樣,使用通配符帶來的靈活性將付出代價,例如,下面是不允許的:

public void addRectangle(List<? extends Shape> shapes){

shapes.add(0,new Rectangle());//編譯時錯誤

}

限制性通配符的一個例子是,是一個人口普查的例子,我們假設數據是由一個名字映射一個人,名字是字符串,人(可以是Person,或是它的子類Driver),Map<k,v>是一個泛型的例子,它擁有兩個參數,表示為一個KEY和value的映射MAP

再次注意正規參數的命名規則,K代表key,V代表value

public class Census{

public static void addRegistry(Map<String ? extends Person> Registry){ }

}

Map<String,Driver> allDrivers =;

census.addResigtry(allDrivers);

編寫泛型類要注意:
   1) 在定義一個泛型類的時候,在 “<>”之間定義形式類型參數,例如:“class TestGen<K,V>”,其中“K” , “V”不代表值,而是表示類型。
   2) 實例化泛型對象的時候,一定要在類名後面指定類型參數的值(類型),一共要有兩次書寫。例如:
TestGen<String,String> t=new TestGen<String,String>();
   3) 泛型中<K extends Object>,extends並不代表繼承,它是類型范圍限制。

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等。看看下圖也許能直觀些請看上圖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);

}

}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved