Java基礎加強總結(二)——泛型

來源:互聯網
上載者:User

標籤:pes   access   不同的   命名   靜態方法   i++   icc   test   其他   

一、體驗泛型

  JDK1.5之前的集合類中存在的問題——可以往集合中加入任意類型的對象,例如下面代碼:

 1 package cn.gacl.generic.summary; 2  3 import java.util.ArrayList; 4  5 public class GenericTest { 6      7     public static void main(String[] args) { 8         /** 9          * 不使用泛型之前ArrayList容器可以儲存任意類型的對象10          */11         ArrayList collection1 = new ArrayList();12         collection1.add(1);//儲存Integer對象13         collection1.add(1L);//儲存Long對象14         collection1.add("xdp");//儲存String對象15         /**16          * 這裡會報異常: JAVA.LANG.CLASSCASTEXCEPTION: 17          * JAVA.LANG.LONG CANNOT BE CAST TO JAVA.LANG.INTEGER18          * 19          */20         int i = (Integer) collection1.get(1);21     }22 }

  JDK1.5之後的集合類希望你在定義集合時,明確表示你要向集合中裝哪種類型的資料,無法加入指定類型之外的資料,例如下面的代碼:

        /**         * 使用泛型限定ArrayList容器只能儲存字串類型的對象         */        ArrayList<String> collection2 = new ArrayList<String>();        collection2.add("孤傲蒼狼");        //collection2.add(1);//報錯,因為限制了collection2隻能儲存String類的對象,不能加入Integer類型的對象        //collection2.add(1L);//報錯,因為限制了collection2隻能儲存String類的對象,不能加入Long類型的對象        //由於已經指明集合中儲存的是字串類型的對象,因此這裡不用再強制轉型了        String element = collection2.get(0);

  泛型是提供給Javac編譯器看的,可以限定集合中的輸入類型,讓編譯器擋住來源程式中的非法輸入,編譯器編譯帶參數類型說明的集合時會去去除掉“類型”資訊,使程式運行不受影響,對於參數化的泛型型別,getClass()方法的傳回值和原始類型完全一樣,由於編譯產生的位元組碼會去掉泛型的類型資訊,因此只要能跳過編譯器,就可以往某個泛型集合中加入其他類型的資料。

  例如下面的代碼就示範了"使用反射得到集合,然後調用add方法往原本只能儲存Integer對象的集合中儲存一個String類型的對象"

1 ArrayList<Integer> collection3 = new ArrayList<Integer>();2 //對於參數化的泛型型別,getClass()方法的傳回值和原始類型完全一樣3 System.out.println(collection3.getClass());//結果為:java.util.ArrayList4 System.out.println(collection3.getClass() == collection2.getClass());//結果為true5 //使用反射得到集合,然後調用add方法往原本只能儲存Integer對象的集合中儲存一個String類型的對象6 collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");7 System.out.println(collection3.get(0));//輸出的結果為:abc,這證明字串對象確實是儲存到了原本只能儲存Integer對象的集合中

備忘:

  1. 泛型是JDK1.5的所有新特性中最難深入掌握的部分,沒有使用泛型時,只要是對象,不管是什麼類型的對象,都可以儲存進同一個集合中,使用泛型集合,可以將一個集合中的元素限定為一個特定類型,這樣集合中就只能儲存同一類型的對象,這樣更安全;並且當從集合中擷取一個對象時,編譯器也知道這個對象的類型,不需要對對象進行強制類型轉換,這樣更方便。
  2. 在JDK1.5之後,你還可以按原來的方式將各種不同類型的資料放到同一個集合中,但是編譯時間會報一個unChecked警告
  3. 泛型中的型別參數嚴格說明集合中裝載的資料類型是什麼和可以加入什麼類型的資料,記住:Collection<String>和Collectin<Object>是兩個沒有轉換關係的參數化類別型
二、瞭解泛型

 

  • ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:
    • 整個稱為ArrayList<E>泛型型別
    • ArrayList<E>中的E稱為類型變數或型別參數
    • 整個ArrayList<Integer>稱為參數化型別
    • ArrayList<Integer>中的Integer稱為型別參數的執行個體或實際型別參數
    • ArrayList<Integer>中的<>是“typeof”
    • ArrayList稱為原始類型
  • 參數化型別與原始類型的相容性:
    • 參數化型別可以引用一個原始類型的對象,編譯時間編譯器會警示告,例如:Collection<String> c = new Vector();
    • 原始類型可以引用一個參數化型別的對象,編譯時間編譯器會警示告,例如:Collection c = new Vector<String>();
  • 參數化型別不考慮型別參數的繼承關係:
    • Vector<String> v = new Vector<Object>();//錯誤,文法上不通過
    • Vector<Object> v = new Vector<String>();//錯誤,文法上不通過

  假設Vector<String> v = new Vector<Object>;可以的話,那麼以後從v中取出的對象當作String用,而v實際指向的集合中可以加入任意類型的對象,

  假設Vector< Object > v = new Vector< String >;可以的話,那麼以後可以向v中加入任意類型的對象,而v實際指向的集合中只能裝String類型的對象

思考:下面的代碼會報錯嗎?(不會報錯)

  • Vector v1 = new Vector<String>();//參數化型別的對象可以給原始類型的引用
  • Vector<Object> v=v1;//參數化型別的引用可以指向原始類型的對象
三、泛型中的?萬用字元

問題:定義一個方法,該方法可以列印出任意參數化型別的集合中的所有資料,該方法如何定義呢?

錯誤的定義:

 1     /** 2      * Collection<Object>中的Object只是說明Collection<Object>執行個體對象中的方法接收的參數是Object 3      * Collection<Object>是一種具體的類型,new HashSet<Date>也是一種具體的類型,兩者沒有相容性問題 4      * @param collection 5      */ 6     public static void printCollection(Collection<Object> collection){ 7         for(Object obj:collection){ 8             System.out.println(obj); 9         }10         collection.add("abc");//沒錯11         collection=new HashSet<Date>();//會報告錯誤12     }

正確的定義:

 1     /**這裡的Collection<?>中的?表示可以傳人任意的型別參數 2      * Collection<?> cols可以匹配任意參數化類別型,但是到底匹配的是什麼類型,只有以後才知道 3      * 所以 cols=new ArrayList<Integer>和cols = new ArrayList<String>都可以 4      * 但是cols.add("abc")或cols.add(new Date())都不行 5      */ 6     public static void printCollection(Collection<?> collection){ 7         for(Object obj:collection){ 8             System.out.println(obj); 9         }10         //collection.add("abc");//報錯,因為collection不知道未來匹配的一定是String類型11         collection.size();//不報錯,此方法與參數類型沒有關係12         collection=new HashSet<Date>();//這是可以的13     } 

  總結:使用?萬用字元可以引用其他各種參數化類別型,?萬用字元定義的變數主要用作引用,可以調用與參數無關的方法,不能調用與參數有關的方法

四、泛型中的?萬用字元的擴充

1.限定萬用字元?的上邊界

  • 正確的寫法:Vector<? extends Number> x = new Vector<Integer>();

   這裡指的是?所代表的參數化型別必須是繼承Number類的,如這裡的?所代表的Integer類型就是繼承Number類的

  • 錯誤的寫法:Vector<? extends Number> x = new Vector<String>();

2.限定萬用字元?的下邊界

  • 正確的寫法:Vector<? super Integer> y = new Vector<Number>();

  這裡指的是?所代表的參數化型別必須是Integer類的父類,如這裡的?所代表的Number類型就是Integer類的父類

  • 錯誤的寫法:Vector<? super Integer> y = new Vector<Byte>();
五、泛型的綜合應用
 1 package cn.itcast.day2; 2 import java.util.HashMap; 3 import java.util.HashSet; 4 import java.util.Map; 5 import java.util.Set; 6 /** 7  * 此類是用來示範泛型的應用的 8  *  9  * @author 孤傲蒼狼10  * 11  */12 public class GenericCase {13     public static void main(String[] args) {14         HashMap<String, Integer> maps = new HashMap<String, Integer>();15         maps.put("lhm", 35);16         maps.put("flx", 33);17         /**18          * 變數的命名技巧:如果以後不知道一個變數該如何命名,就可以以方法名的形式來命名,19          * 如果要定義變數接收傳回值,如果此時不知道如何定義變數名時,就直接定義成returnValue20          */21         Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();// 這裡的變數名直接以方法名的形式定義22         // 使用增強for迴圈迭代Map容器中的key和value23         //這裡的Entry是Map類的一個內部類,map類中儲存的key和value都是封裝在這個Entry內部類中的24         //這個Entry內部類提供了getKey方法取出鍵,getValue方法取出值25         for (Map.Entry<String, Integer> entry : entrySet) {26             System.out.println(entry.getKey() + ":" + entry.getValue());27         }28     }29 }

在JSP頁面中也經常要使用迭代標籤<c:forEach>對Set或Map集合進行迭代:

1 <c:forEach items=”${map}” var=”entry”>2        ${entry.key}:${entry.value}3 </c:forEach>
六、自訂泛型方法
 1 package cn.itcast.day2; 2 import java.io.Serializable; 3 /** 4  * 此類是用來示範如何定義和使用泛型方法的 5  *  6  * @author 孤傲蒼狼 7  *  8  */ 9 public class GenericMethod {10     public static void main(String[] args) {11         add(3, 5);12         Number x1 = add(3.5, 5);// Integer類型和Double類型的交集就是Number類,Number類是這兩個類的父類,所以可以定義Number類型的變數來接收傳回值13         Object x2 = add(3, "abc");// Integer類型和String類型的交集就是Object類,因為Object類是所有類的父類,所以可以定義Object類型的變數來接收傳回值14         /**15          * swap(new String[]{"abc","123","xdp"},1,2);的執行結果如下: 16          * abc 123 xdp 17          * abc xdp 12318          * 從結果來看,索引為1的元素和索引為2的元素的確是交換了位置19          */20         swap(new String[] { "abc", "123", "xdp" }, 1, 2);// 調用自訂泛型方法,傳入的實際參數必須是參考型別的數組21         // swap(new int[]{1,2,3,4,5},1,3);//只有參考型別才能作為泛型方法的實際參數,這裡的int[]數組是屬於基本類型,不能作為泛型方法的參數,所以這樣會報錯22         printArray(new Integer[]{1,2,3});//可以傳入Integer類型的數組,因為Integer類型的數組是屬於參考型別的23         //printArray(new int[]{10,2,5});不能傳入非參考型別的數組作為泛型方法的實際參數24     }25     /**26      * 泛型方法的定義文法: 這裡定義的就是一個泛型方法 方法的傳回值類型是T,即任意的類型 傳回值的具體類型由傳入的型別參數來定27      * 28      * @param <T>29      *            代表任意的類型30      * @param x31      * @param y32      * @return33      */34     private static <T> T add(T x, T y) {35         return null;36     }37     /**38      * 定義一個泛型方法來交換數組中指定索引位置的兩個元素 這裡傳入的數組可以是任意類型的數組39      * 傳入泛型方法的實際參數類型必須是參考型別的數組,不能是基本類型的數組40      * 41      * @param <T>42      * @param a43      * @param i44      * @param j45      */46     private static <T> void swap(T[] a, int i, int j) {47         // 數組中元素位置未交換前的列印結果48         printArray(a);49         T temp = a[i];50         a[i] = a[j];51         a[j] = temp;52         System.out.println();53         // 數組中元素位置交換後的列印結果54         printArray(a);55     }56     /**57      * 定義列印任意引用數群組類型的方法58      * 59      * @param <T>60      * @param array61      */62     private static <T> void printArray(T[] array) {63         for (T t : array) {64             System.out.print(t + "\t");65         }66     }67     /**68      * 定義有extends限定符,並且具有多個上邊界的泛型方法,各個邊界使用&符號分隔69      * @param <T>70      */71     public <T extends Serializable & Cloneable> void method(){}72 }

  普通方法,構造方法和靜態方法都可以使用泛型

七、泛型方法練習題
  1. 編寫一個泛型方法,自動將Object類型對象轉換為其他類型
  2. 定義一個泛型方法,可以將任意類型的數組中的所有元素填充為相應類型的某個對象
  3. 採用自定泛型方法的方式列印出任意參數化型別的集合中的所有內容。
  4. 定義一個泛型方法,把任意參數類型的集合中的資料安全地複製到相應類型的數組中
  5. 定義一個泛型方法,把任意參數類型的一個數組中的資料安全地複製到相應類型的另一個數組中去
 1 /** 2      * 1.編寫一個泛型方法,自動將Object類型對象轉換為其他類型 3      * @param <T> 4      * @param obj 5      * @return 6      */ 7     private static <T> T autoConvert(Object obj){ 8         return (T)obj; 9     }10     /**11      * 2.定義一個泛型方法,可以將任意類型的數組中的所有元素填充為相應類型的某個對象12      * @param <T>13      * @param array14      * @param obj15      */16     private static <T> void fillArray(T[] array,T obj){17         for(int i=0;i<array.length;i++){18             array[i]=obj;19         }20         printArray(array);21     }22     /**23      * 3.採用自定泛型方法的方式列印出任意參數化型別的集合中的所有內容24      * @param <T>25      * @param collection26      */27     private static <T> void printCollection(Collection<T> collection){28         System.out.println(collection.size());29         for(Object obj:collection){30             System.out.println(obj);31         }32     }33     /**34      * 4.定義一個泛型方法,把任意參數類型的集合中的資料安全地複製到相應類型的數組中35      * @param <T>36      * @param srcCollection37      * @param descArray38      */39     private static <T> void CollectionCopyToarray(Collection<T> srcCollection,T[] descArray){40         Iterator<T> it = srcCollection.iterator();41         int recordElementPostion=0;42         while(it.hasNext()){43             descArray[recordElementPostion]=it.next();44             recordElementPostion++;45         }46         printArray(descArray);47     }48     /**49      * 5.定義一個泛型方法,把任意參數類型的一個數組中的資料安全地複製到相應類型的另一個數組中去50      * @param <T>51      * @param srcArray52      * @param descArray53      */54     private static <T> void srcArrayToDescArray(T[] srcArray,T[] descArray){55         for(int i=0;i<srcArray.length;i++){56             descArray[i]=srcArray[i];57         }58         printArray(descArray);59     }60     private static <T> void printArray(T[] array) {61         for (T t : array) {62             System.out.print(t + "\t");63         }64     }
八、自訂泛型類

  如果類的執行個體對象中有多處都要用到同一個泛型參數,即這些地方引用的泛型型別要保持同一個實際類型時,這時就要採用泛型型別的方式定義,也就是類層級的泛型,文法格式如下:

 1 package cn.itcast.day2; 2 import java.util.Set; 3 import cn.itcast.day1.ReflectField; 4 /** 5  * DAO:Data Access Object(Data Access Objects) 6  * 資料訪問:CRUD,即增刪改查 7  * @author 孤傲蒼狼 8  * 此類是用來示範如何定義泛型類 9  * 此泛型類中的<E>中的E代表實際操作的類型10  * 指明了操作類型E之後,GenericDAO類中定義的CRUD方法就都是針對於指定的類型11  */12 public class GenericDAO<E> {13     private E field1; //定義泛型型別的成員變數14     public <E> void add(E x){15     }16     public <E> E findById(int id){17         return null;18     }19     public void delete(E obj){20     }21     public void delete(int id){22     }23     public void update(E obj){24     }25     //public static void update(E obj){}這樣定義會報錯,靜態方法不允許使用泛型參數26     public static<E> void update2(E obj){}//這樣定義就可以,此時的這個靜態方法所針對的類型和GenericDAO<E>中指定的類型是兩個不同的類型27     public Set<E> findByConditions(String where){28         return null;29     }30     public static void main(String[] args) {31         GenericDAO<ReflectField> dao = new GenericDAO<ReflectField>();//這裡指定泛型類的操作類型是ReflectField32         dao.add(new ReflectField(1,3));33         ReflectField rf = dao.findById(1);34         GenericDAO<String> dao1=null;35         new GenericDAO<String>();36     }37 }

  類層級的泛型是根據引用該類名時指定的類型資訊來參數化型別變數的,例如,如下的兩種方式都可以:

    GenericDAO<String> dao=null;

    new GenericDAO<String>();

注意:

  1.在對泛型型別進行參數化時,型別參數的執行個體必須是參考型別,不能是基本類型

  2.當一個變數被聲明為泛型時,只能被執行個體變數和方法調用(還有內嵌類型),而不能被靜態變數和靜態方法調用,因為靜態成員是被所有參數化類別所共用的,所以靜態成員不應該有類層級的型別參數。

 

九、通過反射獲得泛型的實際型別參數
 1 package cn.itcast.day2; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.ParameterizedType; 4 import java.lang.reflect.Type; 5 import java.util.Date; 6 import java.util.Vector; 7 /** 8  * 此類是用來示範如何通過反射獲得泛型的實際型別參數 9  * Hibernate中的原始碼就有這樣的寫法10  * @author 孤傲蒼狼11  * 12  */13 public class UseReflectGetGenericParameter {14     public static void main(String[] args) throws Exception {15         /**16          * 通過這種方式得到的位元組碼中是沒有辦法得到泛型類的實際型別參數的,17          * 因為在編譯這個泛型類時就已經把這個泛型類的實際參數給去掉了18          * Vector<Date> v = new Vector<Date>();19          *  v.getClass();20          */21         Method applyMethod = UseReflectGetGenericParameter.class.getMethod(22                 "applyVector", Vector.class);23         //得到泛型型別的參數化型別數組,Type類是Class類的父類24         Type[] types = applyMethod.getGenericParameterTypes();25         /**26          * ParameterizedType這個類是一個參數化型別類,types數組中儲存的都是參數化型別的參數,27          * 這裡取出第一個數組元素,並強制轉換成ParameterizedType類型28          */29         ParameterizedType pType = (ParameterizedType) types[0];30         System.out.println(pType.getRawType()/*得到原始類型,輸出的結果為:class java.util.Vector*/);31         System.out.println(pType.getActualTypeArguments()[0]/*獲得泛型的實際型別參數,輸出的結果為:class java.util.Date*/);32     }33     /**34      * 利用反射可以得到這個方法的參數列表的類型35      * 通過這個變數v是沒有辦法知道定義它的那個類型的36      * 但是當把這個變數交給一個方法作為參數或者傳回值去使用,37      * Method類中提供了一系列方法可以獲得方法的參數列表38      * 並且是以泛型的那種形式來獲得參數列表39      * @param v40      */41     public static void applyVector(Vector<Date> v) {42     }43 }  

 Java泛型中的標記符含義: 

 E - Element (在集合中使用,因為集合中存放的是元素)

 T - Type(Java 類)

 K - Key(鍵)

 V - Value(值)

 N - Number(數實值型別)

? -  表示不確定的java類型

 S、U、V  - 2nd、3rd、4th types

 

Object跟這些標記符代表的java類型有啥區別呢?  
Object是所有類的根類,任何類的對象都可以設定給該Object引用變數,使用的時候可能需要類型強制轉換,但是用使用了泛型T、E等這些標識符後,在實際用之前類型就已經確定了,不需要再進行類型強制轉換。

Java基礎加強總結(二)——泛型

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.