程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java泛型<>內各種參數的異同,java異同

Java泛型<>內各種參數的異同,java異同

編輯:JAVA綜合教程

Java泛型<>內各種參數的異同,java異同


  先說下本篇隨筆主要涉及到的東西(參考Java編程思想一書):  

    1、說明 List<Fruit> 與 List<Apple> 之間為什麼是非繼承關系。

    2、由 1 引出的問題說明 List<? extends Fruit> 存在的必要性。

    3、說明 List<? super Fruit> 與 List<? extends Fruit> 的區別及 List<? super Fruit> 存在的必要性。

    4、說明 <? extends Fruit> 與 <T extends Fruit> 區別。

    5、說明 原生List 與 List<?> 區別。

    6、解釋自限定泛型 class SelfBound<T extends SelfBound<T>>{ }。

    

    下面將會用到的三個有繼承關系的類:

    

class Fruit {
}
class Apple extends Fruit {
}
class Orange extends Fruit{
}

 

  一、 List<Fruit> 與 List<Apple> 之間為什麼是非繼承關系。

    我認為以下兩個原因可以解釋這個問題:

    1、Java中泛型是後來引入的特性,為了兼容之前的代碼,泛型是存在擦除機制的,List<Fruit> 與 List<Apple> 在擦除後的class中均為List,並不存在繼承關系。

    2、從邏輯上解釋不能有繼承關系的原因:

      

1         public void test(List<Fruit> list) {
2           list.add(new Orange());
3       }    

      在上面的代碼中,test方法接收一個List<Fruit> 類型的list,並在此list 中插入了一個Orange對象,這個方法是沒有任何問題的。現在假設List<Fruit> 與 List<Apple> 間存在繼承關系,那麼此方法可以接收一個List<Apple> 類型的list 參數作為方法的參數,然而之後在方法中就會在一個聲明是List<Apple> 的list 中插入一個 Orange 對象,這顯然是不符合邏輯的。所以 List<Fruit> 與 List<Apple> 之間應該是非繼承關系。

 

  二、 List<? extends Fruit> 存在的必要性。

      由一的介紹我們可以知道 test 方法只能只能接受 List<Fruit> 而不能接受 List<Apple> 類型的 list, 現在我想寫一個方法,既能接受List<Fruit> 又能接收 List<Apple> 類型的 List,應該怎麼做呢? 這時就要用到 List<? extends Fruit> 來實現這個功能了。

      List<? extends Fruit> 表示此 list持有的對象類型是 Fruit 或者從 Fruit 導出的類型(Apple 或者 Orange),相當於為 List 持有的對象類型規定了一個上界。 

      我們只需將上面的test 方法改為 :

     

 1 public class TestGen {
 2     
 3     public void test(List<? extends Fruit> list) {
 4         /*由於傳入的參數是 List<? extends Fruit> 參數的list,這個list是不能進行add 操作的。
 5         list.add(new Fruit());
 6         list.add(new Orange());
 7         list.add(new Apple());*/
 8     }
 9     
10     public static void main(String[] args) {
11         
12         List<Apple> list = new ArrayList<Apple>();
13         List<Orange> list1 = new ArrayList<Orange>();
14          List<Fruit> list2 = new ArrayList<Fruit>();
15         
16          TestGen tg = new TestGen();
17          
18          tg.test(list);
19          tg.test(list1);
20          tg.test(list2);
21          
22     }
23 }

      現在test 方法裡的參數 變為了 List<? extends Fruit> list, 在 main 方法裡可以看到此方法可以接受 List<Apple>,List<orange>,List<Fruit>多種類型的 list,實現了我們想要的功能。

      但是在上述代碼的 test 方法當中我們也可以看到,作為參數傳入的 list 是不能進行 add 操作的,無論 add 的是什麼類型,這是為什麼呢?

      原來由於傳入的 List 類型是 List<? extends Fruit> list, 在JDK源碼中可以看到 List的 add 方法的泛型參數就變為了 <? extends Fruit>,編譯器並不能了解這裡需要 add 的是哪個具體子類型,因此它

    不會接受任何類型的Fruit,編譯器將直接拒絕對參數列表中涉及通配符的方法的調用。

      那麼我們怎麼才能做到向含有通配符的泛型限制的list 中做插入操作呢? 這時我們就要用到 <? super Fruit>參數。

 

  三、List<? super Fruit> 與 List<? extends Fruit> 的區別及 List<? super Fruit> 存在的必要性。

      由二可知, 要想向含有通配符的泛型限制的list 中做插入操作, 此泛型限制必須為  <? super someClass>。 前面已經說到,List<? extends Fruit> 為此List 可以持有的對象類型規定了一個上

    界,即持有的對象只能為 Fruit 或 Fruit 的子類型。 相應地, List<? super Fruit> 則為 List 可以持有的對象類型規定了一個下界,即持有的對象只能為 Fruit 或 Fruit 的超類。

      因此若將 test 方法改為下面這樣,則 add 操作可以進行。

      

1     public void test(List<? super Fruit> list) {
2         list.add(new Fruit());
3         list.add(new Orange());
4         list.add(new Apple());
5     }    

 

      因為我們可以確定 傳入 test 方法的參數至少也是一個 持有 Fruit 類型的 List,所以我們向此 list 中加入 Fruit 及Fruit 的子類型的對象是沒有任何問題的。

  

  四、<? extends Fruit> 與 <T extends Fruit> 區別。

      兩個東西應用的場景是不同的,<T extends Fruit>作用於方法或者類上,而 <? extends Fruit> 則不可以。下面舉例說明。

      

 1 public class TestGen<T extends Fruit> {
 2     private T value;
 3     
 4     public TestGen(T value) {
 5         this.value = value;
 6     }
 7     
 8     public T getValue() {
 9         return value;
10     }
11 
12     public void setValue(T value) {
13         this.value = value;
14     }
15     
16     public <E extends Fruit> void test1(E e) {
17         System.out.println(e.getClass().getName());
18     }
19     
20     public static void main(String[] args) {
21         
22         TestGen<? extends Fruit> tg = new TestGen<Fruit>(new Fruit());
23         //由於 setValue 方法參數列表中涉及通配符(setValue方法中的  T 相當於 ? extends Fruit),setValue方法不能調用。
24         //tg.setValue(new Fruit()); 
25         
26         tg.test1(new Fruit());
27         //tg.test1(new Object()); Object並不是Fruit的子類型,並不能作為參數傳入test1方法。
28     }
29 }

      在代碼中可以看到,類上的限定 <T extends Fruit> 及方法上的限定 <E extends Fruit> 使得類和方法能接受的類型均為 Fruit 及 Fruit 的子類型。

 

  五、原生List 與 List<?> 區別。

    無界通配符 ? 與 原生類型看似好像是等價的,但無界通配符可以有以下含義:“我是想用Java的泛型來編寫這段代碼,我在這裡並不是要用原生類型,但是在當前這種情況下,泛型參數可以持有任何類型”。

    使用無界通配符的  List<?> 要比 原生 List 要安全些。 在原生 list 中,我們可以隨意進行插入操作,可以向同一個 list 中既插入 object 對象,又插入 Fruit,Apple 對象,而 List<?> 是不允許進行 add 操作的。 

 

  六、解釋自限定泛型 class A<T extends A<T>>{ }。

    在 Java 泛型中,這算是一種經常出現的慣用法,這種用法意味著 基類 (這裡的類A) 使用其導出類代替其參數,泛型基類變成了一種其所有導出類的公共公能的模板,但是這些功能對於其所有參數和返回值,將使用導出類型。

   下面舉例說明。

    

 1 class A <T extends A<T>> {
 2     void set(T arg) {
 3         System.out.println("in A");
 4     }
 5 }
 6 //泛型基類變成了一種其所有導出類的公共公能的模板
 7 //導出類B和C均可調用基類的set方法,且只接受導出類對應的類型
 8 class B extends A<B> {
 9     
10 }
11 class C extends A<C> {
12     
13 }
14 public class TestGen {
15     void test2(B b1, B b2, C c1, A a1) {
16         b1.set(b2);
17         //使用自限定泛型時,在導出類中調用的set方法只接受對應的導出類型,不接受基類型和其他的導出類型
18         //b1.set(c1); 不接受其他的導出類型
19         //b1.set(a1); 不接受基類型,編譯器不能識別將類型傳遞給set的嘗試,因為沒有任何方法有這樣的
20         //簽名,實際上,這個參數已經被覆蓋
21     }
22 }

 

 

 

      

      

     

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved