1. 程式人生 > >黑馬程式設計師 java泛型

黑馬程式設計師 java泛型

---------------------- ASP.Net+Unity開發.Net培訓、期待與您交流! ----------------------

一、概念介紹:

Java泛型(generics)是JDK 5中引入的一個新特性,允許在定義類和介面的時候使用型別引數(type parameter)。宣告的型別引數在使用時用具體的型別來替換。泛型最主要的應用是在JDK 5中的新集合類框架中。

二、引入泛型的好處

1、將執行時期出現問題ClassCastException,轉移到了編譯時期。

方便於程式設計師解決問題。讓執行時問題減少,提高了程式安全性。

2、避免了強制轉換麻煩 

  下面是個不用泛型的例子:

List list = new LinkedList(); 
list.add(0); 
Integer x=(Integer)list.iterator().next();

   注意第3行程式碼,這是讓人很不爽的一點,因為程式設計師肯定知道自己儲存在List裡面的物件型別是Integer,但是在返回列表中元素時,還是必須強制轉換型別,這是為什麼呢?原因在於,編譯器只能保證迭代器的next()方法返回的是Object型別的物件,為保證Integer變數的型別安全,所以必須強制轉換。

  這種轉換不僅顯得混亂,更可能導致型別轉換異常ClassCastException,而執行時異常往往讓人難以檢測到。保證列表中的元素為一個特定的資料型別以取消型別轉換,減少發生錯誤的機會,

這應該就是泛型設計的初衷。

下面是用泛型的例子:

List<Integer> list = new LinkedList<Integer>(); 
list.add(0); 
Integer x = list.iterator().next(); 

  在第1行程式碼中指定List中儲存的物件型別為Integer,這樣在獲取列表中的物件時,就不必強制轉換型別了。

三、定義泛型: 

定義泛型跟定義原生型別沒有什麼區別,只是在類或介面後面加入了一個尖括號,尖括號裡面是一個型別引數(定義時就是一個格式化的型別引數,在呼叫時會使用一個具體的型別來替換該型別)

程式碼示例:

//
原始碼中的List介面 和 Iterator 介面的例子 public interface List<E> { void add(E x); Iterator<E> iterator(); } public interface Iterator<E> { E next(); boolean hasNext(); }

四、泛型的特點:

  泛型的最大特就是型別可擦除。

  Java中的泛型基本上都是在編譯期來實現的。在生成的Java位元組程式碼中是不包含泛型中的型別資訊的。使用泛型的時候加上的型別引數,會被編   譯器在編譯的時候去掉。這個過程就稱為型別擦除。

程式碼示例:

public static void main(String[] args)
{
    ArrayList<String>  arr1 = new ArrayList<String>();
    ArrayList<Integer> arr2 = new ArrayList<Integer>();

    System.out.println(arr1.getClass());
    System.out.println(arr2.getClass());
    System.out.println(arr1.getClass() == arr2.getClass());
}

/*執行結果:
   class  java.util.ArrayList     
   class  java.util.ArrayList
    true
*/    

編譯器承擔了全部的型別檢查工作。編譯器禁止某些泛型的使用方式,正是為了確保型別的安全性。

五、泛型的相容性設計

  關於泛型的轉化相容設計:

  程式碼如下:

 //原始型別可以和泛型型別相互賦值  
ArrayList<String> list1 = new ArrayList();  
ArrayList list2 = new ArrayList<String>();  
  
//引數型別不考慮引數的繼承關係,無論是向下轉型還是向上轉型 都會報錯 
ArrayList<String> list3 = new ArrayList<Object>();  //編譯失敗
ArrayList<Object> list4 = new ArrayList<String>();  //編譯失敗
  
//不能建立陣列元素為引數型別的陣列  
ArrayList<String>[] array = new ArrayList<String>[10];  //編譯失敗

六、萬用字元?

先看一個列印集合中所有元素的程式碼。

//不使用泛型時
void printCollection(Collection c) 
{                  
    Iterator i=c.iterator();  

    for (k=0;k < c.size();k++)
    {  
        System.out.println(i.next());  
    } 
}
//使用泛型、、、
void printCollection(Collection<Object> c) 
{   
    for (Object obj:c)
    {  
        System.out.println(i.next());  
    } 
}

     很容易發現,使用泛型的版本也只能接受元素型別為Object型別的集合如ArrayList<Object>();如果是ArrayList<String>,則會編譯時出錯。那麼如何改造新版本以便它能接受所有型別的集合呢?這個問題就可以通過使用萬用字元來解決。修改後的程式碼如下所示:

//使用萬用字元?,表示可以接收任何元素型別的集合作為引數 
void printCollection(Collection<?> c) 
{   
    for (Object obj:c)
    {  
        System.out.println(i.next());  
    } 
}

  這裡使用了萬用字元?指定可以使用任何型別的集合作為引數。讀取的元素使用了Object型別來表示,這是安全的,因為所有的類都是Object的子類。 

  但是還有另外一個問題:

  如下程式碼所示,如果試圖往使用萬用字元?的集合中加入物件,就會在編譯時出現錯誤。需要注意的是,這裡不管加入什麼型別的物件都會出錯。這是因為萬用字元?表示該集合儲存的元素型別未知,可以是任何型別。往集合中加入元素需要是一個未知元素型別的子型別,正因為該集合儲存的元素型別未知,所以我們沒法向該集合中新增任何元素。一的例外是null,因為null是所有型別的子型別,所以儘管元素型別不知道,但是null一定是它的子型別。

Collection<?> c = new ArrayList<String>();  
    c.add(newObject()); //不管加入什麼物件都出錯,除了null外。  
    c.add(null); //OK  

解決辦法:邊界萬用字元

1)?extends萬用字元

假定有一個畫圖的應用,可以畫各種形狀的圖形,如矩形和圓形等。為了在程式裡面表示,定義如下的類層次:

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(Canvasc) { ... }  
}  
public void drawAll(List<?exends Shape> shapes)
{  
    for (Shapes:shapes)
    {  
        s.draw(this);  
    }  
} 

  這裡就又有個問題要注意了,如果我們希望在

                      List<?exends Shape> shapes       中加入一個矩形物件,如下所示:

                      shapes.add(0, new Rectangle()); //compile-time error

那麼這時會出現一個編譯時錯誤,原因在於:我們只知道shapes中的元素時Shape型別的子型別,具體是什麼子型別我們並不清楚,所以我們不能往shapes中加入任何型別的物件。不過我們在取出其中物件時,可以使用Shape型別來取值,因為雖然我們不知道列表中的元素型別具體是什麼型別,但是我們肯定的是它一定是Shape類的子型別。

2)?super萬用字元

       這裡還有一種邊界萬用字元為?super。比如下面的程式碼:

List<Shape> shapes = new ArrayList<Shape>();  
    List<? super Cicle> cicleSupers = shapes;  
    cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK  
    cicleSupers.add(new Shape()); //ERROR

這表示cicleSupers列表儲存的元素為Cicle的超類,因此我們可以往其中加入Cicle物件或者Cicle的子類物件,但是不能加入Shape物件。這裡的原因在於列表cicleSupers儲存的元素型別為Cicle的超類,但是具體是Cicle的什麼超類並不清楚。但是我們可以確定的是隻要是Cicle或者Circle的子類,則一定是與該元素類別相容。

3) 邊界萬用字元總結    

如果想從一個數據型別裡獲取資料,使用 ? extends 萬用字元
如果想把物件寫入一個數據結構裡,使用 ? super 萬用字元
如果既想存,又想取,那就別用萬用字元。

---------------------- ASP.Net+Unity開發.Net培訓、期待與您交流! ----------------------