Java泛型理解


java泛型

通俗的講,泛型就是操作類型的占位符

 

一、通常泛型的寫法

 

1.1定義泛型類

public class ApiResult<T>{

    int resultCode;

    String resultMsg;

    T resultObject;

}

 

1.2定義泛型方法

 

public JsonUtil{

    public <T> T str2Json(String jsonText, Class target){

        T result = null;

        ...

 

        return result;

    }

}

 

1.3 K,V類型

public class ResultMap<K,V>{

    private K key;

    private V value;

 

    // 省略set,get方法

 

 

    public void put(K key, V value){

        this.key = key;

        this.value = value;

    }

}

 

 

二、使用泛型類和方法

ApiResult<User> result = new ApiResult<User>();

result.setResultCode(0);

result.setResultMsg("success");

result.setResultObject(new User());

 

String userJsonText = "userJsonText";

User u = JsonUtil.str2Json(jsonText, User.class);

 

 

ResultMap<String,User> resultMap = new ResultMap<String,User>();

resultMap.put("currentUserKey", new User());

 

 

三、類型擦除

public class Operate{

    public static void main(String[] args){

        List<String> names = new ArrayList<String>();

        names.add("Jack");

        names.add("Tom");

        for(String name:names){

            System.out.println(name);

        }

    }

}

其對應的class文件反匯編以后,我們使用java-gui反編譯.exe  查看編譯之后的代碼如下:

 

發現沒有,根本沒有<String> 這一部分了。這個限制為String類型的泛型被“擦除”了。寫代碼的時候,泛型會做校驗,類型不對應的,無法add,但是編譯之后邊去掉了泛型類型。

 

 

四、為什么要使用java泛型??

這里的泛型就相當於“約法三章”,先給你定好“規矩”,我這個List<String> 就是用來操作String類型的,你插入Person對象就不行。說白了就是為了類型安全。所以其好處有:

4.1類型安全

通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼注釋中)。

4.2消除強制類型轉換

//該代碼不使用泛型:

List li = new ArrayList();

li.put(new Integer(3));

Integer i = (Integer) li.get(0);

 

 

//該代碼使用泛型:

List<Integer> li = new ArrayList<Integer>();

li.put(new Integer(3));

Integer i = li.get(0);

 


 

 

<? extends T><? super T>

 

public class Fruit {

 

}

 

public class Apple extends Fruit {

 

}

 

public class Plate<T> {

       private T item;

       public Plate(T t){

              item = t;

       }

       public T getItem() {

              return item;

       }

       public void setItem(T item) {

              this.item = item;

       }

      

}

 

public class TestGenericParadigm {

      

       public static void main(String[] args){

             

             

              Plate<Fruit> p = new Plate<Apple>(new Apple()); // 編譯不通過

              Plate<Fruit> p = new Plate<Fruit>(new Fruit());

       }

}

為什么上面這個會出現編譯不通過??

解釋:蘋果是水果,但是裝蘋果的盤子不是裝水果的盤子

所以,就算容器里裝的東西之間有繼承關系,但容器之間是沒有繼承關系的

 

為了讓泛型用起來更舒服,Sun的大腦袋們就想出了<? extends T>和<? super T>的辦法,來讓”水果盤子“和”蘋果盤子“之間發生關系

 

 

 

什么是上界?

 

Plate<? extends Fruit>

翻譯成人話就是:一個能放水果以及一切是水果派生類的盤子。再直白點就是:啥水果都能放的盤子。這和我們人類的邏輯就比較接近了。Plate<? extends Fruit>和Plate<Apple>最大的區別就是:Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基類。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”賦值了。

Plate<? extends Fruit> p=new Plate<Apple>(new Apple());

 

如果把Fruit和Apple的例子再擴展一下

//Lev 1

class Food{}

 

//Lev 2

class Fruit extends Food{}

class Meat extends Food{}

 

//Lev 3

class Apple extends Fruit{}

class Banana extends Fruit{}

class Pork extends Meat{}

class Beef extends Meat{}

 

//Lev 4

class RedApple extends Apple{}

class GreenApple extends Apple{}

在這個體系中,上界通配符 Plate<? extends Fruit> 覆蓋下圖中藍色的區域

 

 

 

 

什么是下界?

 

Plate<? super Fruit>

表達的就是相反的概念:一個能放水果以及一切是水果基類的盤子。Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類。對應剛才那個例子,Plate<? super Fruit>覆蓋下圖中紅色的區域。

 

 

 

 

上下界通配符的副作用

 

邊界讓Java不同泛型之間的轉換更容易了。但不要忘記,這樣的轉換也有一定的副作用。那就是容器的部分功能可能失效。

還是以剛才的Plate為例。我們可以對盤子做兩件事,往盤子里set()新東西,以及從盤子里get()東西。

 

public class TestGenericParadigm {

      

       public static void main(String[] args){

              /*

               * 上界通配符

               * 不能存入任何元素

               * 讀出來的東西只能放在Fruit或它的基類中

               */

             

              Plate<? extends Fruit> p = new Plate<Apple>(new Apple());

 

              p.setItem(new Fruit()); //Error

              p.setItem(new Apple()); //Error

             

              Fruit newFruit1 = p.getItem();

              Object newFruit2 = p.getItem();

              Apple new Fruit3 = p.getItem();//Error

             

       }

}

 

原因是編譯器只知道容器內是Fruit或者它的派生類,但具體是什么類型不知道

所以通配符<?>和類型參數的區別就在於,對編譯器來說所有的T都代表同一種類型。比如下面這個泛型方法里,三個T都指代同一個類型,要么都是String,要么都是Integer。

但通配符<?>沒有這種約束,Plate<?>單純的就表示:盤子里放了一個東西,是什么我不知道

 

下界<? super T>不影響往里存,但往外取只能放在Object對象里

使用下界<? super Fruit>會使從盤子里取東西的get( )方法部分失效,只能存放到Object對象里。set( )方法正常。

              /*

               * 下界通配符

               * 存入元素正常

               * 讀取的東西只能存放在Object中

               */

              Plate<? super Fruit> p1 = new Plate<Fruit>(new Fruit());

             

              p1.setItem(new Fruit());

              p1.setItem(new Apple());

             

              Apple newFruit4 = p1.getItem();//Error

              Fruit newFruit5 = p1.getItem();//Error

              Object newFruit6 = p1.getItem();

因為下界規定了元素的最小粒度的下限,實際上是放松了容器元素的類型控制。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以。但往外讀取元素就費勁了,只有所有類的基類Object對象才能裝下。但這樣的話,元素的類型信息就全部丟失

 

PECS原則

最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經很好理解了:

頻繁往外讀取內容的,適合用上界Extends。

經常往里插入的,適合用下界Super。

 

 


免責聲明!

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



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