Java泛型(Generics)詳解


1、導讀

        泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。Generic有“類的,屬性的”之意,在Java中代表泛型。泛型作為一種安全機制而產生。

2、為何需要泛型?

       我們知道集合(Collection、Map之類的容器)是用來存儲任意對象(Object)的一系列“容器類或者接口”,注意這里的“任意對象”,就是指我們可以在這些類或接口中存放任意類型的對象,但這些對象在存儲之前都需要統一向上轉型為Object類型(因為Object類是所以類的的父類),比如類ArrayList就屬於“集合類”,我們可以用這個類來存儲任意對象:

package bean;

import java.util.ArrayList;

public class ArrayListDemo {
    public static void main(String[] args) {
        
        //首先我們創建一個ArrayList對象
        ArrayList al = new ArrayList();
        
        //往"容器"添加元素:我們添加了字符串"哈哈",數字2,boolean類型true,最后我們甚至添加了自己。
        al.add("哈哈");
        al.add(2);
        al.add(true);
        al.addAll(al);
        //由於ArrayList類覆蓋了toString(),這里我們可以直接打印查看效果:
        System.out.println(al);
    }
}

        最后打印出來的字符串是:[哈哈, 2, true, 哈哈, 2, true]

      值得注意的是:這里的2不是int而是Integer,因為ArrayList只能存對象,而“添加它自己”addAll(al)的實質是將al中的所有元素再存入al中,正如打印效果顯示的一樣,我們成功添加了類型不一樣的對象:有String,有Integer、還有Boolean。當然,你可以添加任意你想要添加的對象。因為這些對象在存入“容器”al中時會全部轉型為Object類型,有沒有覺得這樣的“容器”用起來會很爽,什么東西都能往里放。但是,便捷往往會付出相應代價。

  我們將各種類型的元素存入“容器”中當然不是讓它睡上一覺,我們需要將存儲的元素取出使用。對於這些“容器”,我們一般使用迭代器Iterator來獲取每一個元素。(這里不進行迭代器使用說明,只需理解迭代器是取出"容器"中元素的工具就行).

        假設現在我們需要對我們上面創建的“容器”al中的元素進行操作:打印容器中字符串的長度,即“哈哈”的長度。

 1 package bean;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5 
 6 public class ArrayListDemo {
 7     public static void main(String[] args) {
 8         //首先我們創建一個ArrayList對象,並添加對象
 9         ArrayList al = new ArrayList();
10         al.add("哈哈");
11         al.add(2);
12         al.add(true);
13         
14         //使用迭代器Iterator進行遍歷,並打印容器中字符串的長度:
15         //注:next()方法是按照容器的存儲順序,逐一取出第一個到最后一個對象。
16         Iterator i = al.iterator();
17         while(i.hasNext()){
18             String s = (String) i.next();
19             System.out.println(s.length());
20         }
21     }
22     
23 }

       這是運行結果:

        我們來進行代碼分析:while循環中,先用i.next()方法取出第一個元素"哈哈",然后裝換為String類型(因為存進去的時候是Object類型,使用的使用要向下轉型),所以接下來打印了“哈哈”的長度=2。接着循環:迭代器取出第二個元素:2,但這是Integer類型不能強制裝換為String類型,所以拋出異常:ClassCastException:類型轉換異常。

         這就是“能存萬物”所帶來的代價:我們在使用“容器”存儲對象並調用時就換存在“類型轉換異常”的安全隱患,但這需要在運行后才能得知,編譯時無法發現。所以我們該如何解決這個問題呢?我們發現問題在於“類型轉換異常”,所以我們只要保證“容器”中存儲的是同一類型的元素就不會出現這個異常了。類似數組的定義一樣:如 int[] 就表示專門用來存儲int類型數據的數組,String[] 就是用來存儲String類型數據的數組。我們只要將“容器”標上相應類型的標簽就可以了。看到這里,你應該已經理解了泛型的由來,這就是泛型的設計:引入類型參數的概念,即對對象類型進行申明。

 3、泛型的定義格式

       格式為:類名<類型名>,如:

  

 1 package bean;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5 
 6 public class ArrayListDemo {
 7     public static void main(String[] args) {
 8         //首先我們創建一個ArrayList對象,並申明泛型類型為String類型:只能存儲String類型的對象
 9         ArrayList<String> al = new ArrayList<String>();
10         al.add("哈哈");
11         al.add("asdafa");
12         al.add("tobeyourself");
13         
14         //使用迭代器Iterator進行遍歷,並打印容器中字符串的長度:這里迭代器也使用了泛型
15         Iterator<String> i = al.iterator();
16         while(i.hasNext()){
17             String s = (String) i.next();
18             System.out.println(s+"的長度為="+s.length());
19         }
20     }
21     
22 }

       運行結果為:

 3、泛型的好處

        除了解決上面的安全隱患即拋出異常問題,泛型的類或接口在取出對象時將不需要再進行向下類型轉換,因為存儲的時候就是該類型 。

       另外,泛型的使用讓安全問題在編譯時就報錯而不是運行后拋出異常,這樣便於程序員及時准確地發現問題。

4、什么時候使用泛型?

    一般類或接口在api文檔中的定義中帶有<E>標識的,在使用的時候都需要使用泛型機制。如ArrayList<E>、Iterator<E>。

 5、泛型的擦除

        泛型是運用在編譯時期的技術:編譯時編譯器會按照<類型名>的類型對容器中的元素進行檢查,檢查不匹配,就編譯失敗。如果全部檢查成功,則編譯通過,但,編譯通過后產生的.class文件中並沒有<類型名>這個標識,即類文件中沒有泛型,這就是泛型的擦除。

         一句話總結就是:在.java文件運用泛型技術時,編譯器在文件編譯通過后自動擦除泛型標識。

         由於泛型的擦除,類文件中沒有泛型機制,同時也沒有使用向下類型轉換,那么為何運行時無異常?

 6、泛型的補償

          編譯器在擦除泛型后,會自動將類型轉換為原定義的"泛型",這樣就不必再做向下類型轉換了。

         泛型的擦除和補償 這兩個機制都是編譯器內部自動完成的,了解其原理即可。

7、泛型的應用

  7.1【泛型類】

          所謂的泛型類就是運用到泛型技術的類,如上面講到的ArrayList<E>,Iterator<E>等都是java中的泛型類,這些類都是Java已經定義好的泛型類,直接使用就可以了。但有時候我們會遇到這樣的問題:

          假設我們現在有兩個自定義類:Worker類和Student類,現在我們需要一個工具類Tool來獲取Worker對象和Student對象,並能對對象進行操作。

    分析:

          我們可能會想到將Worker和Student類作為Tool類的成員變量,以此來實現對這兩個類的操作。但這樣有一個問題,就是如果這不是兩個類而是很多類,甚至說無數個類,即Tool類可以操作任意類,這種通過添加成員變量來實現調用對象的方法顯然不可行。注意這個“添加任意對象”,這不就是上面說的ArrrayList<E>這些類所具有的特點嗎?泛型類就是為了解決“添加任意對象”而產生的,這里提到的ArrayList<E>屬於已定義泛型類(Java中自帶的),這里我們要用到Tool類來“存儲任意對象”,所以要將Tool類定義為泛型類,這就是根據需求自己設計的自定義泛型類:

首先我們先簡單的定義一下:Worker類和Student類:

Worker類:

 

 1 package bean;
 2 
 3 public class Worker {
 4     private String name;
 5     private int age;
 6     Worker(){
 7         
 8     }
 9     Worker(String name,int age){
10         this.name = name;
11         this.age = age;
12     }
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19     public int getAge() {
20         return age;
21     }
22     public void setAge(int age) {
23         this.age = age;
24     }
25     
26 }

 

Student類:

 1 package bean;
 2 
 3 public class Student {
 4     private String name;
 5     private String sex;
 6     Student(){
 7         
 8     }
 9     Student(String name,String sex){
10         this.name = name;
11         this.sex = sex;
12     }
13     public String getName() {
14         return name;
15     }
16     public void setName(String name) {
17         this.name = name;
18     }
19     public String getSex() {
20         return sex;
21     }
22     public void setSex(String sex) {
23         this.sex = sex;
24     }
25     
26 }

現在我們需要將Tool類定義為泛型類:(注意格式)

 1 package bean;
 2 
 3 public class Tool<E> {
 4     private E e;
 5     
 6     public Tool(E e1){
 7         this.e = e1;
 8     }
 9 
10     public E getE() {
11         return e;
12     }
13     public void setE(E e) {
14         this.e = e;
15     }
16     
17 }

        這里我們寫的很簡單,但已經滿足需求了,重點在於泛型類的寫法:Tool<E>,這里的E字母可以寫你自己喜歡的代碼,這也就是類型參數的應用,E相當於一個類型參數,代表了Tool<E>可以傳入任意對象,下面我們具體使用來看看效果:

 1 package bean;
 2 
 3 class Tooltest{
 4     public static void main(String[] args) {
 5         //假設有兩個對象:一個學生,一個工人
 6         Student t1 = new Student("張三","男");
 7         Worker w1 = new Worker("李四",20);
 8         
 9         //現在我們使用Tool類來調用t1和w1
10         Tool<Student> ts = new Tool<Student>(t1);
11         Tool<Worker> tw = new Tool<Worker>(w1);
12         
13         //打印查看效果
14         System.out.println("使用ts調用t1中的數據:"+ts.getE().getName()+":"+ts.getE().getSex());
15         System.out.println("使用tw調用w1中的數據:"+tw.getE().getName()+":"+tw.getE().getAge());
16     }
17 }

打印結果為:

使用ts調用t1中的數據:張三:男
使用tw調用w1中的數據:李四:20

 7.2【泛型方法】

        泛型方法類似於靜態類的設計,一般的方法傳入的參數是固定類型的,如public void show(int i){}這個方法的參數類型固定為int,但是泛型方法可以傳入指定的參數類型。一般泛型定義有兩種形式:

        1)使用泛型類的參數類型來定義(常用):

         如泛型類Tool<QQ>,它的泛型參數即為QQ。那么泛型方法可以這樣寫:public void show(QQ qq){}.

         2)使用自定義的參數類型來定義:

         如果我們需要自定義參數類型,那么我們把泛型參數放在方法上就可以了(放在返回值類型之前):public <AA> void show(AA aa){}。

         注:靜態方法不能訪問類的泛型,如果需要泛型,我們只能使用方法2),在方法上使用泛型即可:public static <AA> void show(AA aa){}

 7.3【泛型接口】

        和上面一樣的道理,當我們不確定使用對象的類型時,運用泛型就可以解決問題,泛型接口和泛型類的使用是一樣一樣的。

       我們只需要注意,在實現泛型接口時有兩種情況:這里假設有泛型接口interf<AA>,它的實現類是Tool。

       1)確定實現的泛型接口的參數類型:

        假設Tool類需要String類型的參數,那么實現可以直接寫:class Tool implements interf<String>{}即可。

       2)不確定實現的泛型接口的參數類型:

       這時候我們需要泛型類來實現classTool<BB> implements interf<BB>{}.

8、泛型の通配符:?

        當我們不確定傳入的對象類型時我們就可以使用?來代替。“?”即泛型通配符。

9、泛型的限定

        我們知道使用泛型類時:如果明確參數類型,那么泛型就代表一種類型;如果使用通配符?,那么泛型就代表任意類型。但有時候我們希望指定某些類型(不是一個,也不要所有)能作為參數類型,這應該怎么辦呢?

        Java中利用泛型的限定解決了這個問題,即泛型的限定。我們只需要按這樣的格式書寫:

       上限:<? extends E>表示參數類型是E及其所有子類。

       下限:<? super E>表示參數類型是E及其所有超類(即父類)。

10、總結

         泛型即“類型參數化”,用的多了就懂了。


免責聲明!

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



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