前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Java泛型的上下界

Java泛型的上下界

作者头像
玖柒的小窝
修改2021-10-25 10:34:36
修改2021-10-25 10:34:36
50800
代码可运行
举报
文章被收录于专栏:各类技术文章~各类技术文章~
运行总次数:0
代码可运行

1. 准备工作

  • 有如下类的继承关系,为下文理解做好准备

2. 没有泛型上下界遇到了什么问题?

Apple是Fruit的子类,那么如下代码是不会出错的

代码语言:javascript
代码运行次数:0
复制
// ok
Fruit apple = new Apple(); 
复制代码

如果我们写如下代码,定义一个装有Fruit的List,并将装有Apple的List赋值给它,会如何呢?

代码语言:javascript
代码运行次数:0
复制
List<Fruit> plate = new ArrayList<Apple>();
复制代码

它会在Idea里报红线,运行会报错:java: 不兼容的类型: java.util.ArrayList<Apple>无法转换为java.util.List<Fruit>,显然在集合间不存在继承引用关系

那么面对以上问题,就需要上下界登场


3. 泛型的上界, ? extends T

使用泛型的上界,? extends Fruit,就能解决如上问题

代码语言:javascript
代码运行次数:0
复制
List<? extends Fruit> plate = new ArrayList<Apple>();
复制代码
  • 那么?我们该如何理解上界?

? 是java的通配符,在如上的例子中,上界? extends Fruit代表任何继承了Fruit的子类(包含Fruit本身),该集合中装的就是这些元素

  • 那上界有什么特点

只能取,不能存 先说只能取,这个很好理解,集合中都是Fruit的子类,那么取出来的元素必然都能用Fruit来进行引用,看如下代码很好理解

代码语言:javascript
代码运行次数:0
复制
 List<Apple> appleList = new ArrayList<>();
 appleList.add(new Apple());
 appleList.add(new Apple());
 
 List<? extends Fruit> plate = appleList;
 Fruit fruit = plate.get(0);
复制代码

不能存又该如何理解呢?你不是说了列表里边都是Fruit的子类了吗?那我们向里边随便加入它的子类,怎么就不行呢?

且听我慢慢道来!

这段代码报了红线,确实不让添加,显示的错误如下图所示

代码语言:javascript
代码运行次数:0
复制
// 泛型为Apple
List<Apple> appleList = new ArrayList<>(); 
List<? extends Fruit> plate = appleList;
复制代码

那我们该先下来想一想,现在plate引用的是这个appleList,而appleList中泛型为Apple,那么也就意味着plate的泛型也该是Apple,我们向其中添加Apple元素没有问题添加RedApple和GreenApple都没有问题,它们都能安全的转为Apple类型

但是!回过头来,我们再看,? extends Fruit,它表示的是任何继承了Fruit的子类,没问题吧,照这么说添加Banana应该也可以啊,Banana也是Fruit的子类啊,但是不行!别忘了,我们已经将plate的指向到了appList,而appleList规定了泛型是Apple,Banana向其中添加,它能转换成Apple吗?显然不能啊,但是它们确实都是Fruit的子类啊,也就是说,为了保证绝对安全,所以就限制了上界不能进行添加,以免发生转型失败的问题

还要说一种比较好玩的情况

代码语言:javascript
代码运行次数:0
复制
 List<? extends Fruit> plate = Arrays.asList(new Apple(), new Banana());

 Fruit apple = plate.get(0);
 Fruit banana = plate.get(1);
复制代码

在这种情况下,这个plate真的成了能装任何水果的盘子,里边有Apple和Banana,取出来之后确实都是Fruit

4. 泛型的下界, ? super T

  • 泛型的下界以? super Fruit为例,它代表的就是任何Fruit的父类,包括了Fruit本身(还有Object哦)
  • 那下界有啥子特点呐?能存,其实也能取,为什么说其实也能取呢,因为我看了一些文章,为了区分上下界,让它们的特点完全相反,都把下界的特点都写成了不能取,其实在代码中实践,能取出来,只不过会使其中的元素类型失效,取出来的元素类型都是Object
  • 先来解释能存,看如下代码,注意它的下界是Apple
代码语言:javascript
代码运行次数:0
复制
// 定义了一个plate,它的下界为 ? super Apple
List<? super Apple> plate = new ArrayList<>();

plate.add(new Apple());
plate.add(new RedApple());
plate.add(new GreenApple());
复制代码

这段代码在编译器里是没有问题,没有报错,正常运行 那有同学就有问题了,下界规定的是任何这个类型的父类,那向其中添加Apple的父类为什么不行?不是说好了可以能向其中存元素的吗?!怎么还报红线了!

我们先思考思考,上文提到的绝对安全,会不会还出现上文中转型失败的情况? 我们看一下? super Aplle的范围,如下图

ok,添加Apple没问题,添加Fruit和Food也没问题,都在下界的范围内,但是,谁能保证你就是添加的这几个类型的元素呢?谁能来保证它的绝对安全呢?不能吧?!我向其中添加Banana,它肯定会出问题啊!

那为什么又让添加Apple及其子类呢,因为它绝对安全,这些都可以安全的转型成Apple类啊,根本不会出啥毛病,向上转型完全不会出问题,所以是可以添加的,下界的能存元素是这个体现

  • 再简单说一下什么叫其实也能取

不过取出来的东西都是Object,使其中元素的类型失效了,而且因为? super Apple这个范围太大了,不知道拿出来是具体什么类型,只能用最高的Object来进行引用了


5. 它能进行怎样的应用?什么是PECS原则?

  • 我们定义一个MyStack,如下,并添加了一个pushAll方法,将传入进来的List集合中的元素全部都压入栈中,但是值得注意的是,参数List<E> fruits没有使用上下界
代码语言:javascript
代码运行次数:0
复制
public class MyStack<E> extends Stack<E> {
    public void pushAll(List<E> fruits) {
        for (E fruit : fruits) {
            push(fruit);
        }
    }
}
复制代码

我们写如下代码后,会有报错,java: 不兼容的类型: java.util.List<Apple>无法转换为java.util.List<Fruit>,和我们最初的问题是一样的,List<Fruit> 不能引用 List<Apple>,那我们将其改成上界,就能解决这个问题

如下

代码语言:javascript
代码运行次数:0
复制
public class MyStack<E> extends Stack<E> {
    public void pushAll(List<? extends E> fruits) {
        for (E fruit : fruits) {
            push(fruit);
        }
    }
}
复制代码

代码问题消失且能正常运行了,也能够将栈中的元素正常取出来

  • 有了pushAll方法,也要写一个对应的popAll方法,如下,并没有规定下界
代码语言:javascript
代码运行次数:0
复制
public void popAll(List<E> fruits) {
    while (!isEmpty()) {
        fruits.add(pop());
    }
}
复制代码

我们写如下代码,想将之前压入栈中的两个Apple拿出来,并放入List<Food>这个集合中,注意这里放入的是Fruit的父类Food,同样还是报了错误,java: 不兼容的类型: java.util.List<Food>无法转换为java.util.List<Fruit>

我们将代码进行修改,添加上下界之后,代码不报错且能正常运行了

代码语言:javascript
代码运行次数:0
复制
public void popAll(List<? super E> fruits) {
    while (!isEmpty()) {
        fruits.add(pop());
    }
}
复制代码
  • PECS原则

该如何理解生产者和消费者呢,? extends E一直从本身中拿元素出来,并将这些拿出来的元素供栈使用,我们将其成为生产者? super E则相反,它是一直向集合中进行添加,就好像给它的东西都被它消费了一样,所以我们称它为消费者

那什么是PECS原则?

Producer Extends,Consumer Super,也就是说,如果一个参数类型是生产者的话,我们将采用? extends T上界,如果一个参数类型是消费者的话,那么就采用的是? super T下界


巨人的肩膀

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 准备工作
  • 2. 没有泛型上下界遇到了什么问题?
  • 3. 泛型的上界, ? extends T
  • 4. 泛型的下界, ? super T
  • 5. 它能进行怎样的应用?什么是PECS原则?
  • 巨人的肩膀
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档