美文网首页
泛型逆变

泛型逆变

作者: 呆呆李宇杰 | 来源:发表于2017-09-17 17:15 被阅读18次

逆变

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

public class SuperTypeWildcards {
    static void writeTo(List<? super Apple> 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 <T> void writeExact(List<T> list, T item) {
        list.add(item);
    }

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

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

    static <T> void writeWithWildcard(List<? super T> 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或者<? super Fruit>`的参数。
下面是一段类型分析。

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

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

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

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

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

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

    static void f3() {
        CovariantReader<Fruit> 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>中返回一个Apple,从List<Fruit>中返回一个Fruit,就像在f1()中看到的那样。因此,只是读取的时候,静态的泛型方法不需要协变类型。
但是,如果有个泛型类,那么当创建这个类的实例的时候,要为这个类确定类型参数。就像在f2()中看到的那样,fruitReader可以从List<Fruit>中读取一个Fruit,因为这就是它的确切类型,但是List<Apple>还应该产生Fruit对象,但是fruitReader不允许传入List<Apple>参数,因为参数是List<Fruit>类型,编译器不允许发生这种转换。
而为了修正这个问题,CovariantReader.readCovariant()方法将接受List<? extends T>,因此,从这个列表中读取一个T是安全的(因为这个列表的所有对象至少是一个T,并且可能是T导出来的对象),所以在f3()中,可以从List<Apple>中读取Fruit了。

相关文章

  • Kotlin学习笔记 - 泛型

    1. 基本用法 2. 型变 型变包括 协变、逆变、不变 三种: 协变:泛型类型与实参的继承关系相同 逆变:泛型类型...

  • 泛型编程中的型变

    在泛型编程中,经常会提到型变。型变分为两种:协变与逆变。协变covariant表示与泛型参数T的变化相同,而逆变c...

  • Kotlin泛型的高级特性(六)

    泛型的高级特性1、泛型实化2、泛型协变3、泛型逆变 泛型实化 在Java中(JDK1.5之后),泛型功能是通过泛型...

  • Objective-C 泛型 协变 逆变

    为什么要使用泛型 如何使用泛型 限制泛型 协变 逆变 为什么要使用泛型 在使用NSArray, NSSet, NS...

  • Scala 通俗易懂 ---- 协变、逆变、不变

    协变、逆变、不变 Scala 语言中协变、逆变、不变是指拥有泛型的类型,在声明和赋值时的对应关系 协变:声明时泛型...

  • Scala 泛型协变与泛型边界

    代码准备 泛型协变 泛型协变、逆变、不变是指拥有泛型的类在声明和赋值时的对应关系。 协变:声明时泛型是父类,赋值时...

  • Kotlin中的协变、逆变和不变

    共1083字,阅读需要2分钟 泛型实参的继承关系对泛型类型的影响 协变:泛型类型与实参的继承关系相同 逆变:泛型类...

  • Java 泛型与通配符

    参考地址:《Java 泛型,你了解类型擦除吗?》 《Java中的逆变与协变》 《java 泛型中 T、E .....

  • Java泛型学习笔记(2)

    书接上回,这次我们来研究下泛型的另一个特性,泛型方法和协变逆变。 Whaaat?什么是协变逆变?别着急我看的时候也...

  • C# 中泛型接口的协变和逆变(抗变)

    在C#中声明泛型接口时,我们可以使用 in 和 out 参数来控制这个泛型是协变还是逆变的,这里逆变有时也被翻译成...

网友评论

      本文标题:泛型逆变

      本文链接:https://www.haomeiwen.com/subject/ffkmsxtx.html