泛型逆变

逆变

除了使用extends通配符外,还可以使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定,甚至或者使用类型参数:(尽管不能对泛型参数给出一个超类型边界);即不能声明。这使得可以安全地传递一个类型对象到泛型参数中。因此,有了超类型通配符,就看向Collection写入了。

public class SuperTypeWildcards {
    static void writeTo(List apples) {
        apples.add(new Apple());
        apples.add(new Jonathan());
        // apples.add(new Fruit()); Error
    }
}

参数applesApple的某种基类型的List,所以向里面添加Apple或者Apple的子类型是安全的。但是既然Apple是下界,那么向这样的List添加Fruits是不安全的。因为这会使List敞开口子,从而可以向里面添加非Apple类型的对象,而这样是违反静态类型安全的。
因此代码中可能会根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。
超类型边界放松了在可以向方法传递的参数上的限制。

public class GenericWriting {
    static  void writeExact(List list, T item) {
        list.add(item);
    }

    static List apples = new ArrayList<>();
    static List fruits = new ArrayList<>();

    static void f1() {
        writeExact(apples, new Apple());
        writeExact(fruits, new Apple());
    }

    static  void writeWithWildcard(List list, T item) {
        list.add(item);
    }

    static void f2(){
        writeWithWildcard(apples,new Apple());
        writeWithWildcard(fruits,new Apple());
    }

    public static void main(String[] args) {
        f1();
        f2();
    }
}

上面的代码中,f1()f2()都能很好的工作(在Java7版本之后)。我们可以很安全的将Fruit及其子类型作为'Fruit或者`的参数。
下面是一段类型分析。

public class GenericReading {
    static  T readExact(List list) {
        return list.get(0);
    }

    static List apples = Arrays.asList(new Apple());
    static List fruits = Arrays.asList(new Fruit());

    static void f1() {
        Apple a = readExact(apples);
        Fruit f = readExact(fruits);
        f = readExact(apples);
    }

    static class Reader {
        T readExact(List list) {
            return list.get(0);
        }
    }

    static void f2() {
        Reader fruitReader = new Reader<>();
        Fruit f = fruitReader.readExact(fruits);
        // Fruit a = fruitReader.readExact(apples); Error
    }

    static class CovariantReader {
        T readCovariant(List list) {
            return list.get(0);
        }
    }

    static void f3() {
        CovariantReader fruitReader = new CovariantReader<>();
        Fruit f = fruitReader.readCovariant(fruits);
        Fruit a = fruitReader.readCovariant(apples);
    }

    public static void main(String[] args) {
        f1();
        f2();
        f3();
    }
}

上面的代码中,f1()用了精确的类型,因此如果使用了这个没有任何通配符的精确类型,就可以向List中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法readExact()可以有效地“适应”每个方法调用,并能够从List中返回一个Apple,从List中返回一个Fruit,就像在f1()中看到的那样。因此,只是读取的时候,静态的泛型方法不需要协变类型。
但是,如果有个泛型类,那么当创建这个类的实例的时候,要为这个类确定类型参数。就像在f2()中看到的那样,fruitReader可以从List中读取一个Fruit,因为这就是它的确切类型,但是List还应该产生Fruit对象,但是fruitReader不允许传入List参数,因为参数是List类型,编译器不允许发生这种转换。
而为了修正这个问题,CovariantReader.readCovariant()方法将接受List,因此,从这个列表中读取一个T是安全的(因为这个列表的所有对象至少是一个T,并且可能是T导出来的对象),所以在f3()中,可以从List中读取Fruit了。

你可能感兴趣的:(泛型逆变)