閱讀67 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java 泛型詳解

在日常的開發中,我們會看到別人的框架很多地方會使用到泛型,泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。泛型的類型參數隻能是類類型(包括自定義類),不能是簡單類型。本篇博客我們就來詳細解析一下泛型的知識。

泛型類定義及使用

使用泛型有什麼好處呢?首先我們先看一個例子,假設我們有兩個類,代碼如下:

#StringClass 
public class StringClass {
    private String x ;
    private String y ;

    public String getY() {
        return y;
    }

    public void setY(String y) {
        this.y = y;
    }

    public String getX() {
        return x;
    }

    public void setX(String x) {
        this.x = x;
    }
}
#IntClass 
public class IntClass {
    private int x ;
    private int y ;

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }
}
觀察上麵兩個類StringClass 和IntClass,他們除了變量類型不一樣,一個是String一個是int以外,其它並沒有什麼區別!那我們能不能合並成一個呢?通過泛型就可以解決,首先看一下泛型的類是怎麼定義的:

public class ObjClass<T> {

    private T x ;
    private T y ;

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }
}
那麼這時候上麵的兩個類就可以通過泛型的設置,相應生成

ObjClass<String> stringClass = new ObjClass<String>();
        stringClass.setX("haha");

        ObjClass<Integer> intClass = new ObjClass<Integer>();
        intClass.setX(100);

        Log.d("yyy", "stringClass:" + stringClass.getX() + ",intClass:" + intClass.getX());
從結果中可以看到,我們通過泛型實現了開篇中StringClass類和IntClass類的效果。

接下來介紹泛型如何定義及使用:

1.首先需要定義泛型:ObjClass

ObjClass ,即在類名後麵加一個尖括號,括號裏是一個大寫字母。這裏寫的是T,其實這個字母可以是任何大寫字母,無論使用哪個字母,意義都是相同的。如果你想學習Java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裏麵有大量的學習資料可以下載

2.在類中使用泛型

這個T表示派生自Object類的任何類,比如String,Integer,Double等等。這裏要注意的是,T一定是派生於Object類的。

private T x ;
    private T y ;

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public T getY() {
        return y;
    }

    public void setY(T y) {
        this.y = y;
    }
3.使用泛型類

泛型類的使用代碼如下:

ObjClass<String> stringClass = new ObjClass<String>();
        stringClass.setX("haha");

        ObjClass<Integer> intClass = new ObjClass<Integer>();
        intClass.setX(100);
首先,需要構造一個實例:

ObjClass<String> stringClass = new ObjClass<String>();
泛型類的構造則需要在類名後添加上,即一對尖括號,中間寫上要傳入的類型。

因為我們構造時,是這樣的:ObjClass,所以在使用的時候也要在ObjClass後加上類型來定義T代表的意義。

尖括號中,你傳進去的是什麼,T就代表什麼類型。這就是泛型的最大作用,我們隻需要考慮邏輯實現,就能拿給各種類來用。

多泛型變量定義

1.多泛型變量定義

我們不止可以在類中設置一個泛型變量T,還可以聲明多個泛型變量,寫法如下:

public class ObjClass<T,U>
也就是在原來的T後麵用逗號隔開,寫上其它的任意大寫字母即可,如果還有多個,依然使用逗號分隔開即可,則我們前麵定義的泛型類就會變成下麵這樣:

public class ObjClass<T,U> {

    private T x ;
    private U y ;

    public T getX() {
        return x;
    }

    public void setX(T x) {
        this.x = x;
    }

    public U getY() {
        return y;
    }

    public void setY(U y) {
        this.y = y;
    }
}
ObjClass<String,Integer> stringClass = new ObjClass<String,Integer>();
        stringClass.setX("haha");
        stringClass.setY(100);
從上麵的代碼中,可以明顯看出,就是在新添加的泛型變量U用法與T是一樣的。

2.泛型的字母規範

雖然在類中聲明泛型任意字母都可以,但為了可讀性,最好遵循以下的規範:

E — Element,常用在java Collection裏,如:  List<E>,Iterator<E>,Set<E>
 K,V — Key,Value,代表Map的鍵值對
 N — Number,數字
 T — Type,類型,如String,Integer等等
泛型接口定義及使用

在接口上定義泛型與在類中定義泛型是一樣的,代碼如下:

interface MsgClass<T> {
    public T getMsg() ;
    public void setMsg(T x);
}
我們可以利用泛型類來構造填充泛型接口

public class Message<T,U> implements MsgClass<T>{

    private T msg;
    @Override
    public T getMsg() {
        return msg;
    }

    @Override
    public void setMsg(T msg) {
        this.msg = msg;
    }
}
在這個類中,我們構造了一個泛型類Message,然後把泛型變量T傳給了MsgClass,這說明接口和泛型類使用的都是同一個泛型變量。

我們還可以構造一個多個泛型變量的類,並繼承自MsgClass接口:

public class Message<T,U> implements MsgClass<T>{
    private U name;
    private T msg;
    @Override
    public T getMsg() {
        return msg;
    }

    @Override
    public void setMsg(T msg) {
        this.msg = msg;
    }

    public U getName() {
        return name;
    }

    public void setName(U name) {
        this.name = name;
    }
}
泛型函數定義及使用

我們不但可以在類聲明中使用泛型,還可以在函數聲明中也使用泛型,使用如下:

public class ObjClass {
    //靜態函數
    public static <T> void StaticMethod(T a) {

    }

    //普通函數
    public <T> void OrgnicMethod(T a) {

    }

}
上麵分別是靜態泛型函數和常規泛型函數的定義方法,與以往方法的唯一不同點就是在返回值前加上來表示泛型變量。

無論哪種泛型方法都有兩種使用方法:

//靜態方法
ObjClass.StaticMethod("adfdsa");//使用方法一
ObjClass.<String>StaticMethod("adfdsa");//使用方法二
//常規方法
ObjClass objClass = new ObjClass();
objClass.OrgnicMethod(new Integer(111));//使用方法一
objClass.<Integer>OrgnicMethod(new Integer(111));//使用方法二
方法一,隱式傳遞了T的類型,這種隱式的傳遞方式,代碼不利於閱讀和維護。因為從外觀根本看不出來你調用的是一個泛型函數。

方法二,例如上麵例子中,將T賦值為Integer類型,這樣OrgnicMethod(T a)傳遞過來的參數如果不是Integer那麼編譯器就會報錯。

當然泛型函數的返回值也可以使用泛型表示:

public static <T> List<T> parseArray(String response,Class<T> object){  
    List<T> modelList = JSON.parseArray(response, object);  
    return modelList;  
}
函數返回值是List類型。和void的泛型函數不同,有返回值的泛型函數要在函數定義的中在返回值前加上標識泛型;還要說明的是,上麵中,使用Class傳遞泛型類Class對象

泛型數組

泛型同樣可以用來定義在數組上

//定義  
        public static <T> T[] fun1(T...msg){  // 接收可變參數    
            return msg ;            // 返回泛型數組    
        }
        //使用  
        public static void main(String args[]){
            Integer i[] = fun1(8,9,8,44) ;
            Integer[] result = fun1(i) ;
        }
定義了一個靜態函數,然後定義返回值為T[],參數為接收的T類型的可變長參數。

泛型的通配符

在開發中對象的引用傳遞(向上向下傳遞)是最常見的,但是,在泛型的操作中,在進行引用傳遞的時候泛型類型必須匹配才可以傳遞,否則不能傳遞。

例如,如下沒有進行泛型類型匹配,一個是String,一個是Object類型。

class Info<T>{
    private T var ;        // 定義泛型變量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){   
        return this.var.toString() ;
    }
};

public class demo1 {
        public static void main(String args[]) {
            // 使用String為泛型類型
            Info<String> i = new Info<String>();        
            i.setVar("ABCD");
            //把String泛型類型的i對象傳遞給Object泛型類型的temp。
            fun(i);                   
        }

        // 接收Object泛型類型的Info對象
        public static void fun(Info<Object> temp) {        
            System.out.println("內容:" + temp);
        }
    }
編譯發生錯誤。

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method fun(Info<Object>) in the type demo1 is not applicable for the arguments (Info<String>)

    at Thread1.demo1.main(demo1.java:18)
泛型對象進行引用傳遞的時候,類型必須一致,如果非要傳遞,則可以將fun方法中Info參數的泛型取消掉(變成 void fun(Info temp))。、

以上確實改進了功能,但是似乎不是很妥當,畢竟之前指定過泛型。

以上程序在fun()方法中使用 "Info<?>" 的代碼形式,表示可以使用任意的泛型類型對象,這樣的話fun()方法定義就合理了,但是使用以上方法也有需要注意的地方,

即:如果使用“?“接收泛型對象的時候,則不能設置被泛型指定的內容。

class Info<T>{
    private T var ;        
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    
        return this.var.toString() ;
    }
};
public class GenericsDemo{
    public static void main(String args[]){
        Info<String> i = new Info<String>() ;       
        i.setVar("ABCD") ;                            
        fun(i) ;
    }
    public static void fun(Info<?> temp){        
        System.out.println("內容:" + temp) ;
    }
};
如果使用”?“意味著可以接收任意的內容,但是此內容無法直接使得用”?“修飾的泛型的對象進行修改。如下就會出問題:

class Info<T>{
    private T var ;        
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){   
        return this.var.toString() ;
    }
};
public class demo1{
    public static void main(String args[]){
        Info<?> i = new Info<String>() ;       
        i.setVar("ABCD") ;                            
    }
};
運行結果:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method setVar(capture#1-of ?) in the type Info<capture#1-of ?> is not applicable for the arguments (String)

    at Thread1.demo1.main(demo1.java:17)
在使用”?“隻能接收,不能修改。

泛型的上限

class Info<T>{
    private T var ;        
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    
        return this.var.toString() ;
    }
};
public class GenericsDemo{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        
        Info<Float> i2 = new Info<Float>() ;            
        i1.setVar(30) ;                                    
        i2.setVar(30.1f) ;                                
        fun(i1) ;
        fun(i2) ;
    }
    public static void fun(Info<? extends Number> temp){    // 隻能接收Number及其Number的子類
        System.out.print(temp + "、") ;
    }
};
運行成功。但是,如果傳入的泛型類型為String的話就不行,因為String不是Number子類。

在類中使用泛型上限。

class Info<T extends Number>{    // 此處泛型隻能是數字類型
    private T var ;        
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){   
        return this.var.toString() ;
    }
};
public class demo1{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // 聲明Integer的泛型對象
    }
};
如果在使用Info的時候設置成String類型,則編譯的時候將會出現錯誤(String不是Number子類)

注意:利用 <? extends Number> 定義的變量,隻可取其中的值,不可修改

原因如下:

因為Info的類型為 Info

泛型的下限

<? super XXX> 表示填充為任意XXX的父類

class Info<T>{
    private T var ;        
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    
        return this.var.toString() ;
    }
};
public class GenericsDemo21{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>() ;        // 
        Info<Object> i2 = new Info<Object>() ;        // 
        i1.setVar("hello") ;
        i2.setVar(new Object()) ;
        fun(i1) ;
        fun(i2) ;
    }
    public static void fun(Info<? super String> temp){    // 隻能接收String或Object類型的泛型,String類的父類隻有Object類
        System.out.print(temp + "、") ;
    }
};
Object類和String類都是String的父類,所有運行成功,但是如果此時用Integer則會出錯,因為integer並不是String父類。

注意:使用super通配符:能存不能取

如何理解呢?假設有3個類,繼承關係如下:

class CEO extends Manager {  
}  

class Manager extends Employee {  
}  

class Employee {  
}
然後書寫如下代碼:

List<? super Manager> list;  
list = new ArrayList<Employee>();  
//存  
list.add(new Employee()); //編譯錯誤  
list.add(new Manager());  
list.add(new CEO());
為什麼而list.add(new Employee());是錯誤的?

因為list裏item的類型是

List<Employee> list = new ArrayList<Employee>();  
list.add(new Manager());  
list.add(new CEO());
在這裏,正因為Manager和CEO都是Employee的子類,在傳進去list.add()後,會被強製轉換為Employee!

現在回過頭來看這個:

List<? super Manager> list;  
list = new ArrayList<Employee>();  
//存  
list.add(new Employee()); //編譯錯誤  
list.add(new Manager());  
list.add(new CEO());
編譯器無法確定 <? super Manager> 的具體類型,但唯一可以確定的是Manager()、CEO()肯定是 <? super Manager> 的子類,所以肯定是可以add進去的。但Employee不一定是 <? super Manager> 的子類,所以不能確定,不能確定的,肯定是不允許的,所以會報編譯錯誤。

最後強調一下, List<? super Manager> list取出的隻能是Object 類型,這裏雖然看起來是能取的,但取出來一個Object類型,是毫無意義的。所以才有了“super通配符:能存不能取”的結論。

總結1)使用?可以接收任意泛型對象。

2)泛型的上限:?extends 類型(能取不能存)。

3)泛型的下限:?super 類型? super 通配符(能存不能取)。

最後更新:2017-04-11 17:00:23

  上一篇:go 盤點2017年大數據、雲計算產業將要舉辦的大會
  下一篇:go 獎勵用戶吐槽 阿裏雲的“用戶回家計劃”意欲何在?