Java泛型通配符

【来自扔物线】

Java中的泛型:

List<TextView> textViews = new ArrayList<TextView>();

其中 List<TextView> 表示这是一个泛型类型为 TextViewList

TextView textView = new Button(context);
// 👆 这是多态

List<Button> buttons = new ArrayList<Button>();
List<TextView> textViews = buttons;
// 👆 多态用在这里会报错 incompatible types: List<Button> cannot be converted to List<TextView>

我们知道 Button 是继承自 TextView 的,根据 Java 多态的特性,第一处赋值是正确的。

但是到了 List<TextView> 的时候 IDE 就报错了,这是因为 Java 的泛型本身具有「不可变性 Invariance」,Java 里面认为 List<TextView>List<Button> 类型并不一致,也就是说,子类的泛型(List<Button>)不属于泛型(List<TextView>)的子类。

Java 的泛型类型会在编译时发生类型擦除,为了保证类型安全,不允许这样赋值。

在 Java 里用数组做类似的事情,是不会报错的,这是因为数组并没有在编译时擦除类型:

TextView[] textViews = new TextView[10];

但是在实际使用中,我们的确会有这种类似的需求,需要实现上面这种赋值。

Java 提供了「泛型通配符」来解决这个问题。

? extends

在 Java 里面是这么解决的:

List<Button> buttons = new ArrayList<Button>();
      👇
List<? extends TextView> textViews = buttons;

这个 ? extends 叫做「上界通配符」,可以使 Java 泛型具有「协变性 Covariance」,协变就是允许上面的赋值是合法的。

在继承关系树中,子类继承自父类,可以认为父类在上,子类在下。extends 限制了泛型类型的父类型,所以叫上界。

它有两层意思:

  • 其中 ? 是个通配符,表示这个 List 的泛型类型是一个未知类型
  • extends 限制了这个未知类型的上界,也就是泛型类型必须满足这个 extends 的限制条件,这里和定义 classextends 关键字有点不一样:
    • 它的范围不仅是所有直接和间接子类,还包括上界定义的父类本身,也就是 TextView
    • 它还有 implements 的意思,即这里的上界也可以是 interface

这里 ButtonTextView 的子类,满足了泛型类型的限制条件,因而能够成功赋值。

根据刚才的描述,下面几种情况都是可以的:

List<? extends TextView> textViews = new ArrayList<TextView>(); // 👈 本身
List<? extends TextView> textViews = new ArrayList<Button>(); // 👈 直接子类
List<? extends TextView> textViews = new ArrayList<RadioButton>(); // 👈 间接子类

一般集合类都包含了 getadd 两种操作,比如 Java 中的 List,它的具体定义如下:

public interface List<E> extends Collection<E>{
    E get(int index);
    boolean add(E e);
    ...
}

上面的代码中,E 就是表示泛型类型的符号(用其他字母甚至单词都可以)。

我们看看在使用了上界通配符之后,List 的使用上有没有什么问题:

List<? extends TextView> textViews = new ArrayList<Button>();
TextView textView = textViews.get(0); // 👈 get 可以
textViews.add(textView);
//             👆 add 会报错,no suitable method found for add(TextView)

前面说到 List<? extends TextView> 的泛型类型是个未知类型 ?,编译器也不确定它是啥类型,只是有个限制条件。

由于它满足 ? extends TextView 的限制条件,所以 get 出来的对象,肯定是 TextView 的子类型,根据多态的特性,能够赋值给 TextView,啰嗦一句,赋值给 View 也是没问题的。

到了 add 操作的时候,我们可以这么理解:

  • List<? extends TextView> 由于类型未知,它可能是 List<Button>,也可能是 List<TextView>
  • 对于前者,显然我们要添加 TextView 是不可以的。
  • 实际情况是编译器无法确定到底属于哪一种,无法继续执行下去,就报错了。

那我干脆不要 extends TextView ,只用通配符 ? 呢?

这样使用 List<?> 其实是 List<? extends Object> 的缩写。

List<Button> buttons = new ArrayList<>();

List<?> list = buttons;
Object obj = list.get(0);

list.add(obj); // 👈 这里还是会报错

和前面的例子一样,编译器没法确定 ? 的类型,所以这里就只能 getObject 对象。

同时编译器为了保证类型安全,也不能向 List<?> 中添加任何类型的对象,理由同上。

由于 add 的这个限制,使用了 ? extends 泛型通配符的 List,只能够向外提供数据被消费,从这个角度来讲,向外提供数据的一方称为「生产者 Producer」。对应的还有一个概念叫「消费者 Consumer」,对应 Java 里面另一个泛型通配符 ? super

? super

先看一下它的写法:

     👇
List<? super Button> buttons = new ArrayList<TextView>();

这个 ? super 叫做「下界通配符」,可以使 Java 泛型具有「逆变性 Contravariance」。

与上界通配符对应,这里 super 限制了通配符 ? 的子类型,所以称之为下界。

它也有两层意思:

  • 通配符 ? 表示 List 的泛型类型是一个未知类型
  • super 限制了这个未知类型的下界,也就是泛型类型必须满足这个 super 的限制条件。
    • super 我们在类的方法里面经常用到,这里的范围不仅包括 Button 的直接和间接父类,也包括下界 Button 本身。
    • super 同样支持 interface

上面的例子中,TextViewButton 的父类型 ,也就能够满足 super 的限制条件,就可以成功赋值了。

根据刚才的描述,下面几种情况都是可以的:

List<? super Button> buttons = new ArrayList<Button>(); // 👈 本身
List<? super Button> buttons = new ArrayList<TextView>(); // 👈 直接父类
List<? super Button> buttons = new ArrayList<Object>(); // 👈 间接父类

下面这种情况是不可以的:

List<? super Button> buttons = new ArrayList<RadioButton>(); // 👈 间接子类

对于使用了下界通配符的 List,我们再看看它的 getadd 操作:

List<? super Button> buttons = new ArrayList<TextView>();
Object object = buttons.get(0); // 👈 get 出来的是 Object 类型
Button object = buttons.get(0); // 👈 按照泛型类型读取,会报错

Button button = new Button(context);
buttons.add(button); // 👈 add 操作是可以的

解释下,首先 ? 表示未知类型,编译器是不确定它的类型的。

虽然不知道它的具体类型,不过在 Java 里任何对象都是 Object 的子类,所以这里能把它赋值给 Object

Button 对象一定是这个未知类型的子类型,根据多态的特性,这里通过 add 添加 Button 对象是合法的。

使用下界通配符 ? super 的泛型 List,只能读取到 Object 对象,一般没有什么实际的使用场景,通常也只拿它来添加数据,也就是消费已有的 List<? super Button>,往里面添加 Button,因此这种泛型类型声明称之为「消费者 Consumer」。


小结下,Java 的泛型本身是不支持协变和逆变的。

  • 可以使用泛型通配符 ? extends 来使泛型支持协变,但是「只能读取不能修改」,这里的修改仅指对泛型集合添加元素,如果是 remove(int index) 以及 clear 当然是可以的。
  • 可以使用泛型通配符 ? super 来使泛型支持逆变,但是「只能修改不能读取」,这里说的不能读取是指不能按照泛型类型读取(除了Object之外的类型),你如果按照 Object 读出来再强转当然也是可以的。

根据前面的说法,这被称为 PECS 法则:「Producer-Extends, Consumer-Super」。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值