泛型边界_3 逆变(Contravariance)

一、超类型通配符(supertype wildcards)

可声明通配符是某特定类的任何父类界定,如< ? super MyClass>,或者< ? super T> ,不过不存在< T super MyClass>,这样可以安全地传递一个类型对象到泛型类型中。有了superType wildcards ,就可以向Collection写入了。

public class SuperTypeContravariant {

   void writeTo(List<? super Apple> apples) {
       apples.add(new Apple());
       Apple apple = (Apple) apples.get(0); // 由于apples 中加入的肯定都是 Apple 或其子类对象,所以向上强转为Apple是可行的
       System.out.println(apple);
       Object object = apples.get(0);  // 用Object接收
       System.out.println(object);
   }

   public static void main(String[] args) {
       new SuperTypeContravariant().writeTo(new ArrayList<>(10));
   }

class Apple{}

说到这里,我们要注意一下怎么样去感性地认识带边界的通配符:
比如: List< ? extends MyClass> list1,即 MyClass是上界:
这里要把 **< ? extends MyClass> 当成一个整体,说明这个List类型是MyClass或其子类类型中确定的某一种,**由于并不知道到底是哪个确定的类型,所以不能add 元素,即使是Object也不行。**不过可以肯定从list1中get到的元素都是MyClass或其子类类型,所以可以从list1中get。

再看:List< ? super MyClass> list2,即MyClass是下界:
这里也把**< ? super MyClass>当成整体,说明这个类型是MyClass或其父类类型中的一种**,无论是确定的哪一种,我们add MyClass或其子类类型的实例都是可以的。而如果add MyClass的父类实例,那就破坏了静态类型安全了。get 元素时,由于并不确定具体的类型,所以只能用Object 去接收。但是由于list2中元素都是Myclass类型或其子类,所以get到元素后向上强转为MyClass也是可行的。

二、superType wildCards的场景

超类通配符放松了能向方法传递的参数上所做的限制。

public class GenericWriting {

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

    static <T> void writeExact(List<T> list, T item) {
        list.add(item);
    }

    static void f1() {
        writeExact(apples, new Apple());
        writeExact(fruits, new Apple());
        // 在旧版本的JDK中,这句报 incompatible types 的异常,所以在
        // writeWithWildCard 中将参数类型变为超类通配符< ? super T>,
        // 这样就可以将T 或其 子类类型的对象作为参数传递给List
        
        // 值得注意的是:在JDK8+版本中,编译器变聪明了,这句代码是正常的,所以就这个例子来说,有点鸡肋
    }

    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();
    }
}

这个Demo中最关键的要注意到:
static < T> void writeWithWildCard(List< ? super T> list, T item) {} 的入参已经变成了
List< ? super T> list ,**这个List将持有T或其子类的某个具体类型。**这样就能将一个T或其子类类型对象作为参数传递到List的方法。

三、协变和通配符

public class GenericReading {

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

    static <T> T readExact(List<T> list) { // 精确类型
        return list.get(0);
    }

    private static void f1() {
        Apple apple = readExact(apples);
        Fruit fruit = readExact(fruits);
        fruit = readExact(apples); // Apple upcast to Fruit
    }

    /*泛型没有内建协变*/
    static class Reader<T> {
        T readExact(List<T> ts) {
            return ts.get(0);
        }
    }

    static void f2() {
        Reader<Fruit> fruitReader = new Reader<>();
        Fruit fruit = fruitReader.readExact(fruits);
//        fruitReader.readExact(apples); // 不兼容的类型: List<Apple>无法转换为 List<Fruit>
    }

    /*泛型边界(协变类型)*/
    static class CovariantReader<T> {
    //从 List<? extends T> list 得到的 元素类型必定是T或其子类类型
        T readCovariant(List<? extends T> list) { 
            return list.get(0);
        }
    }

    static void f3() {
        CovariantReader<Fruit> covariantReader = new CovariantReader<>();
        Fruit f1 = covariantReader.readCovariant(apples); // in fact ,it is an apple!
        Fruit f2 = covariantReader.readCovariant(fruits); // it is surely a fruit!
    }
    public static void main(String[] args) {
        f1();f2();f3();
    }
}

四、测试的demo

public class GenericTest {

    /**
     * 方法 m1 : 具有一个调用了 Generic1 方法的 **逆变参数**:::向一个泛型类型中写(传递给一个方法),所以
     * 用 限下边界。或者说,这是一个 T 消费者
     * @param arg Generic1类的实例,类型为 T或其父类类型中的确定某一种
     */
    <T> void m1(Generic1<? super T> arg, T t) {
        arg.take(t);
    }

    /**
     * 方法m2 : 具有一个调用 Generic2 方法的 **协变参数** ::: 从一个泛型类型中读 ( 从一个方法中返回) ,所以
     * 用 限上边界。或者说,这是一个 T 生产者
     * @param arg Generic2类的实例, 类型为T或其子类类型中的确定某一种
     */
    <T> void m2(Generic2<? extends T> arg) {
        T give = arg.give();
    }

    public static void main(String[] args) {
        GenericTest genericTest = new GenericTest();
        // 报错: cannot be applied
        // 原因:no instances of type variables exist so that pet conform to cat
        // inference variable T has incompatible types:
        // lower bounds: pet
        // upper bounds:Object ,Cat
        // 意思是:按下面这行代码,T 的类型推断就会出错: T 的下界是pet ,上界又是 Object cat
         genericTest.m1(new Generic1<Cat>(), new Pet());


        // 正常
        genericTest.m1(new Generic1<Pet>(), new Cat());
        genericTest.m2(new Generic2<>());
    }
}


/*Generic1 只有一个方法,接收一个T类型的参数*/
class Generic1<T>{
    T t;

    void take(T t) {
        this.t = t;
    }
}

/*Generic2 只有一个方法,返回一个T类型的参数*/
class Generic2<T>{
    T t;

    T give() {
        return t;
    }
}
class Pet{}
class Cat extends Pet{}

猜你喜欢

转载自blog.csdn.net/qq_30118563/article/details/82317084