泛型 - 通配符

2018-11-28  本文已影响0人  平头哥2015

使用通配符的原因: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-name

例子:

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

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


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

语法:

class-name<? extends superclass> var-name

例子:

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.add(new Integer(1)); // error
list.add(null); // OK
Number n = list.get(0); // OK
Integer i = list.get(0); // error
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.add(new Integer(1)); // OK
list.add(new Object()); // error
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原则(Producer Extends Consumer Super)

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

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

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));
    }
}
上一篇下一篇

猜你喜欢

热点阅读