泛型全面分析和應用(一)


       概述

       Java SE5的重大變化之一就是提出了泛型的概念。泛型實現了參數化類型的概念,使代碼可以應用於多種類型中。有很多原因促成了泛型的出現,而最主要的原因,就是為了構建容器類。我們很多時候在寫代碼的過程中,經常是遇到了同一個方法如果傳入或者傳出的參數類型過於單一,同樣的功能的方法我們要寫兩次甚至是多次,原因就是由於參數類型不能適應其他類型的應用。那么為了解決這個問題,Java設計者也是煞費苦心。

 

  • 一個泛型的小例子
  • 泛型中使用注意的方法特性
  • 擦除與其解決方法
  • 泛型技術實例

 

一、一個泛型的小例子

 


      在Java SE5之前,為了寫一個通用的方法,也許得這樣子。用Object來作為一個標准,反正大家都是Object的子類。

 1 package Generic;
 2 
 3 public class Holder {
 4 
 5     private Object a;
 6     public Holder(Object a){
 7         this.a = a;
 8     }
 9     public void set(Object a){
10         this.a = a;
11     }
12     public Object get(){
13         return a;
14     }
15     public static void main(String args[]){
16         Holder h1 = new Holder(new String());
17         h1.set("ss");
18         Holder h2 = new Holder(new Integer(1));
19         h2.set(“asdf”);
20     }
21 }

       大家發現這個問題沒有,既然Hodler h2的set方法的傳入參數是一個Object,那么這個參數就可以是String,Integer,Double,h2其實是想保存的一個Integer類型的數據,但由於Object的泛用性,導致了一個String類型的數據保存了進去。這么來說,這就是一個潛在的隱患了。

       在有些情況下,我們確實希望容器能夠同時持有多種類型的對象。但是,通常來說,我們只會使用容器來存儲一種類型的對象。泛型的主要目的之一就是用來指定容器要持有什么類型的對象,而且有編譯器來保證類型的正確性。因此,與其使用Object,我們更喜歡暫時不指定類型,而是稍后再決定具體使用什么類型。要達到這個目的,需要使用類型參數,用尖括號括住,放在類名后面。然后在使用這個類的時候,再用實際的類型代替此類型參數。

 1 package Generic;
 2 
 3 public class Holder2<T> {
 4 
 5     private T a;
 6     public Holder2(T a){
 7         this.a = a;
 8     }
 9     public void set(T a){
10         this.a = a;
11     }
12     public T get(){
13         return a;
14     }15 }

 

 

二、泛型中使用注意的方法特性

 


 

      由於Object的包容性,導致了參數可能的混亂。所以為了解決這個問題,泛型的提出是一個很好的方案。下面就來介紹下泛型的一些用法和特點,這些特性大都由於泛型的擦除機制所導致,擦除機制是指在運行過程中任何和類型有關的信息都會被擦除,所有在運行中集合的具體信息都被擦除成它們的原生類型。下面通過一個例子來了解:

 

 1 public class TestF {
 2 
 3     public static void main(String[] args) {
 4         Class a1 = new ArrayList<Integer>().getClass();
 5         Class a2 = new ArrayList<String>().getClass();
 6         System.out.println(a1 == a2);
 7        //結果是true
 8     }
 9 
10 }

 

      ArrayList<Integer>和ArrayList<String>的class其實在擦除后都是ArrayList。

 

1.不能有繼承的關系泛型的

 

Vector<Object> v = new Vector<String>(); 
//Type mismatch: cannot convert from Vector<String> to Vector<Object>

     

       不能有繼承的關系泛型。聲明了一個Vector<Object>與Vector<String>這對組合和Object與String這對組合是完全不同的。Object和String這對的確有關系,Object是String的父類,從java的多態Object obj = new String()這個語句是沒有關系的。但是Vector<Object>和Vector<String>的組合就不是繼承關系,在編譯時,其實、Vector<Object> v =new Vector<String>實際上是Vector c = new Vector()這種效果,其實就是java泛型的擦除效果

      其實從另外一個角度去看,Vector<Object>其實就是告訴編譯器,Vector里面要放的是Object,但是在實例化的時候卻放入了String,雖然是繼承關系,但是還是不同的兩個class。當涉及到了集合,看問題的角度提升到 集合 的層面去觀察,里面的泛型類型就一定要一致了(在沒有使用通配符?的情況下要一致,使用了?后面詳細講)

 

2.不能有聲明數組類型的泛型

         為什么不能有聲明數組類型的泛型呢?我看了一下Java SE的官方文檔 http://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html
         里面有兩段代碼很清楚的闡釋了這個問題。

 

 1 // Not really allowed.
 2 List<String>[] lsa = new List<String>[10];
 3 Object o = lsa;
 4 Object[] oa = (Object[]) o;
 5 List<Integer> li = new ArrayList<Integer>();
 6 li.add(new Integer(3));
 7 // Unsound, but passes run time store check
 8 oa[1] = li;
 9 
10 // Run-time error: ClassCastException.
11 String s = lsa[1].get(0);

 

      假設泛型數組允許被建立,如果此時有一個List<String>的數組,但是經過一系類轉換后,居然把List<Integer>給賦給了 List<String>數組,這是明顯的錯誤,但是由於java泛型的擦除機制,程序代碼沒有報錯,此時已經有問題了。

       解決這個問題,就是不要聲明數組類型的泛型,用通配符聲明。

 1 // OK, array of unbounded wildcard type.
 2 List<?>[] lsa = new List<?>[10];
 3 Object o = lsa;
 4 Object[] oa = (Object[]) o;
 5 List<Integer> li = new ArrayList<Integer>();
 6 li.add(new Integer(3));
 7 // Correct.
 8 oa[1] = li;
 9 // Run time error, but cast is explicit.
10 String s = (String) lsa[1].get(0);

        由於通配符,程序一開始不知道List<?>具體是什么類型的的數組,這個時候我們將List<Integer>放入進去,編譯器還好能接受。最后一句,故意從List<Integer>去拿String,這是報了java.lang.ClassCastException的錯誤。

 

3.泛型類不能有static修飾方法

      這個更加不用說了,泛型編譯后其實是擦除了很多信息,很多時候都沒有確定好參數類型,到了運行調用的時候才進行的添加的。而static是靜態關鍵字,被修飾的變量和方法都是在運行一開始就被加載到了虛擬機中,無需要再new對象,一直通過對象引用就好了。這兩種狀態是不一致的,所以不能在泛型類上面加上static。

 

4.泛型參數會由於參數的合集類型、個數、順序一樣而導致方法不能重載。

1     public static void applyVecotr(Vector<Integer> v,Integer i){
2         //泛型里的參數是不能作為區分參數去overload(重載)
3     }
4     public static void applyVecotr(Vector<String> v,Integer i){
5         //泛型里的參數是不能作為區分參數去overload(重載)
6     }

      此時報錯 Erasure of method applyVecotr(Vector<Integer>, Integer) is the same as another method in type GenericTest

 

5.通配符?和泛型類型參數代表符號<T>、<K>、<V>等的關系和區別

     在一個代碼中通配符?用於泛型代表任何的類型,而<T>、<K>、<V>也是一樣功能。但是<T>、<K>、<V>等符號有一個好處,就是可以運用到在整個方法甚至整個類中去,用來區分多個泛型。表示<T>所代表的符號都是同一個類型的參數,而<K>是另外的一種參數。但是對於泛型參數則需要在方法或者類的開頭聲明,而通配符?則不需要的。

1 public class DiffGenericParameter<K,V> {
2     
3     private K k;//代表K為key值
4     private V v;//代表V為value值
5     public DiffGenericParameter(K k,V v){
6         this.v = v;
7         this.k = k;
8     };
9 }

 

三、擦除與其解決方法


      在《java編程思想》中對擦除的核心動機做出了解釋:它使得泛化的客戶端可以用非泛化的類庫來使用,反之亦然,這經常被稱為“遷移兼容性”。在理想情況下,當所有事物都可以同時被泛化時,我們就可以專注於此。在現實中,即使程序員只編寫泛化代碼,他們也必須處理在Java SE5之前編寫的非泛型類庫。那些類庫的作者可能從沒有想過要泛化它們的代碼,或者可能剛剛開始接觸泛型。

      因此Java泛型不僅必須支持向后兼容性,即現有的代碼和類文件仍舊合法,並且繼續保持其之前的含義;而且還要支持遷移兼容性,使得類庫按照它們自己的步調變為泛型的,並且當某個類庫變為泛型時,不會破壞依賴於它的代碼和應用程序。通過允許非泛型代碼與泛型代碼共存,擦除使得這種向着泛型的遷移成為可能。

      盡管如此,但是我們還是可以通過反射得到一些蛛絲馬跡的。通過反射的getGenericParameterTypes去拿到泛型中的參數類型。

 1 package Generic;
 2 
 3 import java.lang.*;
 4 import java.util.*;
 5 
 6 public class GenericTest {
 7 
 8     public static void main(String[] args) throws Exception {
 9         
10         /**
11          * 通過反射去獲取方法的泛型參數化類型,通過其他方式是無法知道泛型的參數類型,因為在泛型中
12          * 編譯后去掉了泛型化。
13          */
14         Method method = GenericTest.class.getMethod("applyVector", Vector.class,Map.class);
15         Object[] ParameterTypes = method.getParameterTypes();
16         //System.out.println(ParameterTypes);
17         /*for(Object ParameterType : ParameterTypes ){
18             System.out.println(ParameterType);
19         }*/
20         ParameterizedType type = (ParameterizedType) method.getGenericParameterTypes()[0];
21         System.out.println("type.getRawType()  = "+type.getRawType());
22         System.out.println("type.getActualTypeArguments = "+type.getActualTypeArguments()[0]);
23     }
24     
25     public static void applyVector(Vector<Date> v, Map<String,String> map){
26         
27     }
28 
29 }

   得到的結果是:

type.getRawType()  = class java.util.Vector
type.getActualTypeArguments = class java.util.Date

      由於有了這個反射能得到泛型的參數類型后,還是挺興奮的。但是我們不能經常去使用反射,這畢竟還是一個耗時耗力的事情。所以在泛型的很多代碼編寫上就有新的嘗試了。

  • 泛型的定義加上了上邊界和下邊界
  • 加入Class類型的代碼

   1.泛型的定義加上了上邊界和下邊界 

      因為擦除移除了類型信息,如果使用無界泛型參數調用的方法只是那些可以用Object調用的方法。如果能夠將某個參數限制為某個類型子集,我們就能調用這個類型子集去調用方法了。所以在Java泛型中用了extends這個關鍵字,但是此時extends和在普通情況下面所具有的意義是完全不同的。

     

 1 package Generic;
 2 
 3 import java.awt.Color;
 4 
 5 interface HasColor{java.awt.Color getColor();}
 6 
 7 class Colored<T extends HasColor>{
 8     T item;
 9     Colored(T item){ this.item = item;}
10     T getItem(){
11         return item;
12     }
13     java.awt.Color color(){return item.getColor();}
14 }
15 
16 class Dimension{public int x , y, z;}
17 
18 //this won't work -- class must be first, then interface;
19 //calss coloredDimension<T extends HasColor & Dimension>
20 
21 class ColoredDimension<T extends Dimension & HasColor>{
22     T item;
23     ColoredDimension(T item){this.item = item;}
24     T getItem(){return item;}
25     java.awt.Color getColor(){return item.getColor();}
26     int getX(){return item.x;}
27     int getY(){return item.y;}
28     int getZ(){return item.z;}
29 }
30 
31 interface Weight{int weight();}
32 
33 class Solid<T extends Dimension & HasColor & Weight>{
34     T item;
35     Solid(T item){this.item = item;}
36     T getItem(){return item;}
37     java.awt.Color color(){return item.getColor();}
38     int getX(){return item.x;}
39     int getY(){return item.y;}
40     int getZ(){return item.z;}
41     int weight(){return item.weight();}
42 }
43 
44 class Bounded extends Dimension implements HasColor ,Weight {
45 
46     @Override
47     public int weight() {
48         // TODO Auto-generated method stub
49         return 0;
50     }
51 
52     @Override
53     public Color getColor() {
54         // TODO Auto-generated method stub
55         return null;
56     }
57     
58 }
59 
60 public class BasicBounds {
61  
62     public static void main(String[] args) {
63         Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
64         System.out.println(solid.weight());
65         System.out.println(solid.color());
66         System.out.println(solid.getY());
67     }
68 
69 }

     所以你會發現,原來extends在泛型使用中不僅僅可以被用來限定上邊界class,還可以用來限定上邊界interface。而且,在此之后我們可以使用class或者interface中的類型變量或者是方法,這樣子我們就可以很好的利用Java提供的這個機制去構建一些泛型關系。

 

2.加入Class類型的代碼

 

     既然被擦除了,那就在調用的時候再將這個類型傳入就好了。這個也是挺不錯的方法之一。

 1 package Generic;
 2 
 3 class ClassAsFactory<T>{
 4     T x;
 5     public ClassAsFactory(Class<T> kind){
 6         try {
 7             x= kind.newInstance();
 8         } catch (InstantiationException e) {
 9             // TODO Auto-generated catch block
10             e.printStackTrace();
11         } catch (IllegalAccessException e) {
12             // TODO Auto-generated catch block
13             e.printStackTrace();
14         }
15     }
16 }
17 
18 class Employee{}
19 
20 public class InstantiateGenericType {
21 
22     public static void main(String[] args) {
23         ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
24         System.out.print("ClassAsFactory<Employee> succeed");
25         ClassAsFactory<Integer>  fi = new ClassAsFactory<Integer>(Integer.class);
26 
27     }
28 }

       發現了沒有,在new實例化這個泛型的時候,我們再將這個實例化的類型傳入,接着再將這個newInstance就好了。

       下一篇,將介紹泛型用於程序設計中的實例,不錯的實例。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM