Java泛型中关于通配符extends,super,捕获转换,协变逆变的理解

1. 通配符 与 T 的区别

T:作用域模板上,用于将数据进行参数化,不能用于实例化对象。
?:在实例化对象的时候,不确定泛型参数的具体类型时,可以使用通配符进行对象的定义。

<T>等同于 <T extends Object >

<?>等同于 <? extends Object >

例一:定义泛型类,将key,value的数据类型进行< K, V >参数化,而不可以使用通配符。

public class Container<K, V> {
    private K key;
    private V value;

    public Container(K k, V v) {
        key = k;
        value = v;
    }
}

例二:实例化泛型对象,我们不能够确定eList存储的数据类型是Integer还是Long,因此我们使用List<? extends Number>定义变量的类型。

List<? extends Number> eList = null;
eList = new ArrayList<Integer>();
eList = new ArrayList<Long>();

2. 上界类型通配符(? extends)

List<? extends Number> eList = null;
eList = new ArrayList<Integer>();
Number numObject = eList.get(0);  //语句1,正确

//Type mismatch: cannot convert from capture#3-of ? extends Number to Integer
Integer intObject = eList.get(0);  //语句2,错误

//The method add(capture#3-of ? extends Number) in the type List<capture#3-of ? extends Number> is not applicable for the arguments (Integer)
eList.add(new Integer(1));  //语句3,错误

语句1:List<? extends Number> eList存放Number及其子类的对象,语句1取出Number(或者Number子类)对象直接赋值给Number类型的变量是符合java规范的。
语句2:List<? extends Number> eList存放Number及其子类的对象,语句2取出Number(或者Number子类)对象直接赋值给Integer类型(Number子类)的变量是不符合java规范的。
语句3:List<? extends Number> eList不能够确定实例化对象的具体类型,因此无法add具体对象至列表中,可能的实例化对象如下。

eList = new ArrayList<Integer>();
eList = new ArrayList<Long>();
eList = new ArrayList<Float>();

总结:上界类型通配符add方法受限,但可以获取列表中的各种类型的数据,并赋值给父类型(extends Number)的引用。因此如果你想从一个数据类型里获取数据,使用 ? extends 通配符。限定通配符总是包括自己。

3. 下界类型通配符(? super )

List<? super Integer> sList = null;
sList = new ArrayList<Number>();

//Type mismatch: cannot convert from capture#5-of ? super Integer to Number
Number numObj = sList.get(0);  //语句1,错误

//Type mismatch: cannot convert from capture#6-of ? super Integer to Integer
Integer intObj = sList.get(0);  //语句2,错误

sList.add(new Integer(1));  //语句3,正确

语句1:List<? super Integer> 无法确定sList中存放的对象的具体类型,因此sList.get获取的值存在不确定性,子类对象的引用无法赋值给兄弟类的引用,父类对象的引用无法赋值给子类的引用,因此语句错误。
语句2:同语句1。
语句3:子类对象的引用可以赋值给父类对象的引用,因此语句正确。
总结:下界类型通配符get方法受限,但可以往列表中添加各种数据类型的对象。因此如果你想把对象写入一个数据结构里,使用 ? super 通配符。限定通配符总是包括自己。

四、捕获转换

通常情况下,使用原生类型和<?>并没有什么区别,但是有一种情况特别需要使用<?>而不是原生类型,即捕获转换(因为未指定的通配符类型被捕获,并转换为确切类型)。

换一种说法就是,如果向一个使用<?>的方法传递原生类型,那么对编译期来说,可能会推断出实际的参数类型,使得这个方法可以回转并调用另一个使用这个确切类型的方法。这种技术被称为捕获转换。

捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从该方法中返回T,因为T对于该方法来说是未知的。

五、协变逆变

逆变,协变与不变。逆变与协变用来描述类型转换后的继承关系
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
f(⋅)是协变(covariant)的,当A≤B时有成立f(A)≤f(B)成立;
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

数组是协变的。比如
Collection[] collections = new ArrayList[]{};

泛型是不变的,需要通配符来实现协变和逆变。

// <? extends>实现了泛型的协变,它意味着某种Collection或Collection子类的类型,规定了该容器持有类型的上限,它是具体的类型,只是这个类型是什么没有人关心  比如:
List<? extends Collection> list = new ArrayList<ArrayList>();

// <? super>实现了泛型的逆变,它意味着某种ArrayList或ArrayList父类的类型,规定了该容器持有类型的下限,比如:
List<? super ArrayList> list = new ArrayList<Collection>(); 

六、总结

  • 限定通配符总是包括自己
  • 上界类型通配符:add方法受限
  • 下界类型通配符:get方法受限
  • 既想存,又想取,那就别用通配符
  • 不能同时声明泛型通配符上界和下界

PS:

对于extends 协变 记住 : 子类对象可以复制给父类引用(故get可用);因为<? extends Fruits>编译器不知道fruits引用指向什么对象(所有子类型都可能,比如List<? extends Fruit>也可以合法的指向List),所以add不能用。

对于super 逆变 记住 :在父类容器中可以放置子类对象(故add可用);因为super只规定了下界,所以get时无法确定用什么类型引用,只能使用Object。

扩展阅读
JAVA泛型杂谈–擦除,协变,逆变,通配符等
Java泛型(二) 协变与逆变

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值