Java 之 類型通配符


一、類型通配符

  當聲明一個方法時,某個形參的類型是一個泛型類或泛型接口類型,但是在聲明方法時,又不確定該泛型實際類型,可以考慮使用類型通配符。

  先來看下面一個案例

1     public static void test(List c){ 2         for (int i = 0; i < c.size(); i++) { 3  System.out.println(c.get(i)); 4  } 5     }

  上面的方法執行是沒有問題的,但是此處使用 List 接口時沒有傳入實際類型參數,這將引起泛型警告。如何消除這個警告呢?

  方式一:

1     public static void test1(List<Object> c){ 2         for (int i = 0; i < c.size(); i++) { 3  System.out.println(c.get(i)); 4  } 5     }

    這樣的形參太局限了,只能傳入 List<Object> 類型的實參。

  方式二:聲明一個泛型方法

1     public static <T> void test2(List<T> c){ 2         for (int i = 0; i < c.size(); i++) { 3  System.out.println(c.get(i)); 4  } 5     }

    該方法需要聲明泛型形參T。

  方式三:使用類型通配符

1     public static void test3(List<?> c){ 2         for (int i = 0; i < c.size(); i++) { 3  System.out.println(c.get(i)); 4  } 5     }

   那么方式二的泛型方法 test2() 和方式三的 test3() 使用類型通配符有什么區別?

   test3方法帶通配符的List僅表示它可以接受指定了任意泛型實參的List,並不能把元素加入其中,例如如下代碼將會引起編譯錯誤:

1     public static void test(List<?> c, String str){ 2  c.add(str); 3     }

    因為我們不知道上面程序中c集合里元素的類型,所以不能向其中添加對象,除了null對象,因為它是所有引用數據類型的實例。

     test2方法帶泛型的List,表示該集合的元素類型是T,因此允許T系列的對象加入其中,例如如下代碼是可行的:

1     public static <T> void test(List<T> c, T t){ 2  c.add(t); 3     }

    即如果不涉及添加元素到帶泛型的集合中,那么兩種方式都可以,如果涉及到添加元素到帶泛型的集合中,使用類型通配符<?>的不支持。

 

二、設定類型通配符的上限

  當直接使用 List<?> 這種形式時,即表明這個 List 集合接收泛型實參指定為任意類型的 List。但有時候不想這樣,只希望接收某些類型的 List。

  Demo:一個圖形的抽象父類 Graphic,兩個子類 Circle和Rectangle,想定義一個方法,可以打印不同圖形的面積。

1     public static void printArea(List<Graphic> graphics){ 2         for (Graphic g : graphics) { 3  System.out.println(g.getArea()); 4  } 5     }

  但是,List<Graphic> 的形參只能接收 List<Graphic>的實參,如果想要接收 List<Circle>,List<Graphic>的集合,可以使用List<?>

1     public static void printArea(List<?> graphics){ 2         for (Object obj : graphics) { 3             Graphic g = (Graphic) obj; 4  System.out.println(g.getArea()); 5  } 6     }

  但是這樣有兩個問題,一個是 List<?> 可以接收任意類型,不僅僅圖形,第二個是需要強制類型轉換。

  為了解決這個問題,Java 允許設定通配符的上限:

<? extends Type>,這個通配符表示它必須是Type本身,或是Type的子類。

   如:

1     public static void printArea(List<? extends Graphic> graphics){ 2         for (Graphic g : graphics) { 3  System.out.println(g.getArea()); 4  } 5     }

  與前面的完全相同,因為不知道這個受限制的通配符的具體類型,所以不能把 Graphic 對象或其子類對象加入到這個泛型集合中。

1 public static void printArea(List<? extends Graphic> graphics){ 2     graphics.add(new Circle());//編譯錯誤,因為不知道?的具體類型,也可能是Rectangle
3 }

   如果需要將 Graphic 對象或其子類對象加入這個泛型集合,那么就只能用泛型方法。

三、設定通配符的下限

  假設自己實現一個工具方法:實現將 src 集合里元素復制到 dest 集合中的功能,因為 dest 集合需要接受 src 的所有元素,所以 dest 集合元素的類型應該是 src 集合元素的父類。為了表示兩個參數之間的類型依賴,考慮同時使用通配符、泛型形參類實現該方法。

  Demo:

1     public static <T> void copy(Collection<T> dest,Collection<? extends T> src){ 2         for (T t : src) { 3  dest.add(t); 4  } 5     }

 

  上面的方法實現了前面的功能。現在假設該方法需要一個返回值,返回最后一個被復制的元素,可以把上面方法修改如下:

1     public static <T> T copy(Collection<T> dest,Collection<? extends T> src){ 2         T last = null; 3         for (T t : src) { 4  dest.add(t); 5             last = t; 6  } 7         return last; 8     }

  表面上看這個實現沒有問題,實際上有一個問題,當遍歷src元素的類型是不確定(但可以肯定是T的子類),程序只能用T來籠統的表示,所以返回值類型是T。

1     public static void main(String[] args) { 2         ArrayList<String> src = new ArrayList<String>(); 3         src.add("Hello"); 4         src.add("World"); 5         
6         ArrayList<Object> dest = new ArrayList<Object>(); 7         
8         Object last = copy(dest, src); 9     }

  發現返回的T是Object,也就是說,程序在復制集合元素的過程中,丟失了src集合元素的類型String。

  對於上面的copy方法,可以這樣理解兩個集合參數之間的依賴關系:不管src集合元素的類型是什么,只要dest集合元素類型與src的元素類相同或是它的父類即可。

  為了表示這種約束關系,Java允許設定通配符的下限:

<? super Type>,這個通配符表示它必須是Type本身或是Type的父類。

  代碼示例:

 1     public static <T> T copy(Collection<? super T> dest,Collection<T> src){  2         T last = null;  3         for (T t : src) {  4  dest.add(t);  5             last = t;  6  }  7         return last;  8  }  9     public static void main(String[] args) { 10         ArrayList<String> src = new ArrayList<String>(); 11         src.add("Hello"); 12         src.add("World"); 13         
14         ArrayList<Object> dest = new ArrayList<Object>(); 15         
16         String last = copy(dest, src); 17     }

  注意:只有類型通配符才可以設定下限,泛型形參是不能設定下限的。

四、泛型方法與方法重載

  因為泛型既允許設定通配符的上限,也允許設定通配符的下限,如果在一個類里包含這樣兩個方法定義,會報錯:

1     public static <T> T copy(Collection<? super T> dest,Collection<T> src){ 2         //....省略代碼
3  } 4     
5     public static <T> T copy(Collection<T> dest,Collection<? extends T> src){ 6         //....省略代碼
7     }


免責聲明!

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



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