Java 之 类型通配符

Posted 格物致知_Tony

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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     }

以上是关于Java 之 类型通配符的主要内容,如果未能解决你的问题,请参考以下文章

Java重要技术(23)泛型之问号通配符

Java之通配符

理解Java泛型 通配符 ? 以及其使用

操作 Java 泛型:泛型在继承方面体现与通配符使用

java 16-8 泛型高级之通配符

Java 泛型概述与应用