Java——聊聊<? extends T>和<? super T>的含义及区别

Posted 宋子浩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——聊聊<? extends T>和<? super T>的含义及区别相关的知识,希望对你有一定的参考价值。

文章目录:

1.简介

2.上界通配符

3.下界通配符

4.简单小总结


1.简介

<? extends T>    <? super T> 是Java泛型中的“通配符(Wildcards)”“边界(Bounds)”的概念。

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

为什么要用通配符和边界?

使用泛型的过程中,经常出现一种很别扭的情况。比如我们有Fruit类,和它的派生类Apple类。

class Fruit 
class Apple extends Fruit 

然后有一个最简单的容器:Plate类。盘子里可以放一个泛型的“东西”。我们可以对这个东西做最简单的“放”和“取”的动作:set( )和get( )方法。

class Plate<T> 
    private T item;

    public Plate(T item) 
        this.item = item;
    

    public T getItem() 
        return item;
    

    public void setItem(T item) 
        this.item = item;
    

现在我定义一个“水果盘子”,逻辑上水果盘子当然可以装苹果。

Fruit fruit = new Apple(); //正确
Plate<Fruit> p = new Plate<Apple>(new Apple()); //报错了

但实际上Java编译器不允许这个操作。会报错,“装苹果的盘子”无法转换成“装水果的盘子”。

实际上,编译器脑袋里认定的逻辑是这样的:

  • 苹果 IS-A 水果
  • 装苹果的盘子 NOT-IS-A 装水果的盘子

所以,就算容器里装的东西之间有继承关系,但容器之间是没有继承关系的。所以我们不可以把Plate的引用传递给Plate。

为了让泛型用起来更舒服,Sun公司的那些大牛们就想出了 <? extends T> 和 <? super T> 的办法,来让”水果盘子“和”苹果盘子“之间发生关系。


2.<? extends T>上界通配符

下面代码就是“上界通配符(Upper Bounds Wildcards)”:

Plate<? extends Fruit>

翻译成人话就是:一个能放水果以及一切是水果派生类的盘子。再直白点就是:啥水果都能放的盘子。这和我们人类的逻辑就比较接近了。Plate<? extends Fruit>Plate<Apple>最大的区别就是:Plate<? extends Fruit>Plate<Fruit>以及Plate<Apple>的基类。直接的好处就是,我们可以用“苹果盘子”给“水果盘子”赋值了。

Plate<? extends Fruit> p = new Plate<Apple>(new Apple());

假设,我有下面这样的继承结构:↓↓↓ 

class Food 

class Fruit extends Food 
class Meat extends Food 

class Apple extends Fruit 
class Banana extends Fruit 
class Beef extends Meat 
class Pork extends Meat 

class RedApple extends Apple 
class GreenApple extends Apple 

此时,上面的 <? extends Fruit> 就表示下图的红色部分。

class MainTest1 
    public static void main(String[] args) 
        Plate<? extends Fruit> plate = new Plate<>(new Apple());
        //不能存入任何元素
//        plate.setItem(new Fruit());
//        plate.setItem(new Apple());

        //读取出来的东西只能存放在Fruit或它的基类里
        Fruit fruit = plate.getItem();
        Food food = plate.getItem();
        Object obj = plate.getItem();
        //Apple apple = plate.getItem(); //报错
    

但是呢,在上界通配符中存在着一定的缺陷:不能往里存,只能往外取。

<? extends Fruit>会使往盘子里放东西的set()方法失效。但取东西get( )方法还有效。比如下面例子里两个set()方法,插入Apple和Fruit都报错。

  • setItem() : 编译器只知道元素类型是 Fruit 或 Fruit 的子类,所以有可能是 Fruit、Apple、Banana、RedApple、GreenApple中的某一个类型,那么当我们向其中添加元素时,编译器并不知道具体是哪一个 (派生) 类。所以这种操作是不允许的。
  • getItem() : 即使编译器不知道此时类、集合中的元素是 Fruit、Apple、Banana、RedApple、GreenApple 中的哪一个,但是能够确定的是这些元素都是由 Fruit 派生出来的,所以上面的代码中 getItem 方法是可以用 Fruit、Fruit的父类Food、总类Object 接收。

3.<? super T>下界通配符

下面代码就是“下界通配符(Upper Bounds Wildcards)”:

Plate<? super Fruit>

表达的就是相反的概念:一个能放Fruit以及一切是Fruit基类的盘子。Plate<? super Fruit>Plate<Fruit>的基类,但不是Plate<Apple>的基类。对应刚才那个例子,Plate<? super Fruit>覆盖下图中红色的区域。 

Plate<? super Fruit> plate = new Plate<>(new Fruit());

假设,我有下面这样的继承结构:↓↓↓ 

class Food 

class Fruit extends Food 
class Meat extends Food 

class Apple extends Fruit 
class Banana extends Fruit 
class Beef extends Meat 
class Pork extends Meat 

class RedApple extends Apple 
class GreenApple extends Apple 

此时,上面的 <? superFruit> 就表示下图的蓝色部分。

class MainTest2 
    public static void main(String[] args) 
        Plate<? super Fruit> plate = new Plate<>(new Fruit());
        //存入元素正常
        plate.setItem(new Fruit());
        plate.setItem(new Apple());
        //读取出来的东西只能存放在Object类中
        //Apple apple = plate.getItem();
        //Fruit fruit = plate.getItem();
        Object obj = plate.getItem();
    

但是呢,在下界通配符中存在着一定的缺陷:下界<? super T>可以往里存,但往外取只能放在Object对象里。

使用下界<? super Fruit>会使从盘子里取东西的get()方法部分失效,只能存放到Object对象里。set( )方法正常。

  • setItem() : 编译器只知道元素类型是 Fruit 或者 Fruit 的基类或父类,所以有可能是 Fruit、Food、Object 其中的一个类型。编译器知道类型的下界是 Fruit ,根据类型向上兼容的特性,所以可以添加的元素是 Fruit 以及 Fruit 的派生类。
  • getItem() : 既然编译器不确定集合类型是 Fruit 或者 Fruit 的基类或父类中的哪一种,可能是Fruit,可能是Food,那么返回类型只能是它们的共同父类 Object。但是这里可以通过强制类型转换成相应的Fruit的子类。

4.简单小总结

最后看一下什么是PECS(Producer Extends Consumer Super)原则,已经很好理解了:

  • <? extends C> 适合大量做获取操作的情景。
  • <? super C> 适合大量做添加操作的情景。

特点:<? extends C> 的 add() 被限制,<? super C> 的 get() 被限制。

以上是关于Java——聊聊<? extends T>和<? super T>的含义及区别的主要内容,如果未能解决你的问题,请参考以下文章

Java——聊聊<? extends T>和<? super T>的含义及区别

Java——聊聊<? extends T>和<? super T>的含义及区别

Java - ? extends T 和 ? super T 理解

java中泛型限定<? extends Comparable<? super T>> 和 <T extends Comparable<? super T>&g

<T extends A<T>> 和继承形式的 Java 泛型

Java泛型<? extends T>和<? super T>