为什么Java中的泛型通配符(<? extends T>和<? super T>)会有限制?

引言

最近在学习kotlin时, 注意到了之前没注意的Java中的类型通配符问题,
为什么一会按照泛型传递的参数结果IDE报错(error: capture of ? extends ...);
一会方法返回值是Object, 但是明明使用了泛型啊!!!

类继承关系

为了方便讲解,文中所有类的继承关系如图所示

image.png

Java 泛型通配符介绍

<? extends T><? super T>是Java泛型中的 “通配符(Wildcards)”

  • <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
  • <? super T>:是指 “下界通配符(Lower Bounds Wildcards)”

当使用<? extends T>通配符定义一个变量,将无法使用这个变量中所用带有泛型参数的方法
例如:

ArrayList<Apple> apples = new ArrayList<>();
apples.add(new Apple());
ArrayList<? extends Fruit> fruits = apples;
// 如果方法的返回值是泛型,可以正常使用
Fruit fruit = fruits.get(0);
// 如果方法参数含有泛型参数,在编译器就会报错
// fruits.add(new Fruit());
// 方法参数不含有泛型参数,可以正常使用
fruits.remove(0);
复制代码

当使用<? super T>通配符定义一个变量,这个变量中所有返回值是泛型的方法都会变成返回Object

ArrayList<Fruit> fruits = new ArrayList<>();
fruits.add(new Apple());
ArrayList<? super Apple> apples = fruits;
// 返回值是泛型的方法只会返回Object类型
Object object = apples.get(0);
// 但是添加没有问题, 可以添加的类型需要时apple及其子类
apples.add(new Apple());
apples.add(new RedApple());
apples.add(new GreenApple());
// apples.add(new Fruit()); // 父类无法添加
复制代码

具有限制的原因

那么让我们思考下Java为什么添加这样的限制了?总不可能是闲的慌吧。
首先我们明确一个问题再Java中是不支持协变(covariant) 的, 有人可能会问这时一个什么东西?
具体可以看看Kotlin教程中泛型这一段的描述
这里我们简单理解的话就是在Java中认为 List<Apple> 不是 List<Fruit>的子类,
下面第两行代码会直接报错 image.png

但是实际的的编码过程中确实存在这种需求, 这时候Java就为我们提供了上面将到的<? extends T><? super T>

<? extends T> 上界通配符


<? extends T> 可用用于标识一个变量的类型,标识可以接受泛型T及其子类类型的值, 就比如上面的例子我们可以修改一下改成ArrayList\<? extends Fruit\> fruits就不会报错了

image.png ArrayList\<? extends Fruit\> fruits实际上可以是如下泛型的数组 image.png

现在我们就可以解释为什么Java要对禁止调用带有泛型参数的方法了,
如果不禁用,试想一下下面的代码存在什么问题

ArrayList<Apple> apples = new ArrayList<>();
ArrayList<? extends Fruit> fruits = apples;
fruits.add(new Banana());  // 实际上无法通过编译, 这里假设可以
Apple apple = apples.get(0);
复制代码

没错当程序运行到第4行的时候会报错,因为Banana类型无法强转成Apple类型,
造成这种问题的原因就是因为ArrayList\<? extends Fruit\> fruits可以引用多种不同泛型的数组,
Java为了防止这种问题发生所有就干脆禁止调用带有泛型参数的方法了,
但是对于上界运算符对于返回值是泛型的方法没有任何约束,因为fruits引用的一定是自己的子类,向上转型没有任何问题

<? super T> 下界通配符

<? extends T> 可用用于标识一个变量的类型,标识可以接受泛型T及其父类类型的值,
假设我们定义一个变量: ArrayList<? super Apple> apples = fruits;, 那么fruits实际上可以引用的泛型数组就是如下图所示:

image.png 这种情况下的对于添加元素没有限制,因为fruits引用的一定是Fruit父泛型的数组, 向这样的数组中添加Fruit类型的子类一点问题也没有,
但是从中获取元素就存在了一个问题, 以为不知道fruits具体引用的是泛型类型的数组,
是ArrayList<Fruit>? 、ArrayList<Food>()、甚至是ArrayList<Object>()都有可能
在这种情况下为了保准一定正确,从fruits取出来的对象就是Object类型

参考:
博客园
kotlin(中文网)
kotlin(英文网)
b站up主抛物线对kotlin泛型的讲解

猜你喜欢

转载自juejin.im/post/7042968539195523109