Java泛型之通配符

原文点此连接javascript

使用通配符的缘由:Java中的数组是协变的,可是泛型不支持协变。php

数组的协变

首先了解下什么是数组的协变,看下面的例子:java

Number[] nums = new Integer[10]; // OK

由于Integer是Number的子类,一个Integer对象也是一个Number对象,因此一个Integer的数组也是一个Number的数组,这就是数组的协变。数组

Java把数组设计成协变的,在必定程度上是有缺陷的。由于尽管把Integer[]赋值给Number[],Integer[]能够向上转型为Number[],可是数据元素的实际类型是Integer,只能向数组中放入Integer或者Integer的子类。若是向数组中放入Number对象或者Number其余子类的对象,对于编译器来讲也是能够经过编译的。可是运行时JVM可以知道数组元素的实际类型是Integer,当其它对象加入数组是就会抛出异常(java.lang.ArrayStoreException)。安全

泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现,因此泛型是不支持协变的。例如:数据结构

List<Number> nums = new ArrayList<Integer>(); // incompatible types

当确实须要创建这种向上转型的类型关系的时候,就须要用到泛型的通配符特性了。例如:测试

List<? extends Number> nums = new ArrayList<Integer>(); // OK

无边界通配符(Unbounded Wildcards)

语法:

class-name<?> var-nameui

例子:

public static void print(List<?> list) {
    for (Object obj : list) {
        System.out.println(o);
    }
}

List<?> list和List list的区别:

  • List<?> list是表示持有某种特定类型对象的List,可是不知道是哪一种类型;List list是表示持有Object类型对象的List。
  • List<?> list由于不知道持有的实际类型,因此不能add任何类型的对象,可是List list由于持有的是Object类型对象,因此能够add任何类型的对象。
    注意:List<?> list能够add(null),由于null是任何引用数据类型都具备的元素。

Pair<?> 和 Pair 的区别

  • Pair<?>的  ? getFirst()方法,返回值只能赋值给一个Object对象,它的void setFirst(? )方法不能被调用,甚至不能用Object调用。
  • 为何要使用这样脆弱的类型?它对于许多简单的操做很是有用。例如 ,下面这个方法将用来测试一个 pair 是否包含一个 mill 引用,它不须要实际的类型。
    public static boolean hasNulls (Pair<?> p)
    {
        return p.getFirstO = null | | p.getSecondO = null ;
    }
    经过将 hasNulls 转换成泛型方法,能够避免使用通配符类型:
    public static <T> boolean hasNulls (Pair<T> p)
    可是,带有通配符的版本可读性更强。

     


上边界限定的通配符(Upper Bounded Wildcards)

语法:

class-name<? extends superclass> var-namespa

例子:

public static double sum(List<? extends Number> list) {
    double s = 0.0;
    for (Number num : list) {
        s += num.doubleValue();
    }
    
    return s;
}

List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error

特性:

  • List<? extends Number> list表示某种特定类型(Number或者Number的子类)对象的List。跟无边界通配符同样,由于没法肯定持有的实际类型,因此这个List也不能add除null外的任何类型的对象
list.add(new Integer(1)); // error
list.add(null); // OK
  • 从list中获取对象是是能够的(好比get(0)),由于在这个List中,无论实际类型是什么,但确定都能转型为Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
  • 事实上,只要是形式参数有使用类型参数的方法,在使用无边界或者上边界限定的通配符的状况下,都不能调用。好比以java.util.ArrayList为例:
public E get(int index) // 能够调用 public int indexOf(Object o) // 能够调用 public boolean add(E e) // 不能调用 

下边界限定的通配符(Lower Bounded wildcards)

语法:

class-name<? super subclass> var-name设计

例子:

public static void writeTo(List<? super Integer> list) {
    // ...
}

List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error

特性:

  • List<? super Integer> list表示某种特定类型(Integer或者Integer的父类)对象的List。能够肯定这个List持有的对象类型确定是Integer或者其父类,因此往list里面add一个Integer或者其子类的对象是安全的,由于Integer或者其子类的对象均可以向上转型为Integer的父类对象。可是由于没法肯定实际类型,因此往list里面add一个Integer的父类对象是不安全的
list.add(new Integer(1)); // OK
list.add(new Object()); // error
  • 当从List<? super Integer> list获取具体的数据的时候,JVM在编译的时候知道实际类型能够是任何Integer的父类,因此为了安全起见,要用一个最顶层的父类对象来指向取出的数据,这样就能够避免发生强制类型转换异常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原则(Producer Extends Consumer Super)

从上面上边界限定的通配符和下边界限定的通配符的特性,能够知道:

  • 对于上边界限定的通配符,没法向其中加入任何对象,可是能够从中正常取出对象。
  • 对于下边界限定的通配符,能够存入subclass对象或者subclass的子类对象,可是取出时只能用Object类型变量指向取出的对象。

简而言之,上边界限定(extends)的通配符适合于内容的获取,而下边界限定(super)的通配符更适合于内容的存入。因此就有了一个PECS原则来很好的解释这两种通配符的使用原则。

  • 当一个数据结构做为producer对外提供数据的时候,应该只能取数据而不能存数据,因此适合使用上边界限定(extends)的通配符。
  • 当一个数据结构做为consumer获取并存入数据的时候,应该只能存数据而不能取数据,因此适合使用下边界限定(super)的通配符。
  • 若是既须要取数据也须要存数据,就不适合使用泛型的通配符。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}
相关文章
相关标签/搜索