上下界通配符

1. 阿里java开发手册-1.5.0.pdf中的1.5.6规则

【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。

说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:

1)频繁往外读取内容的,适合用上界 Extends。

2)经常往里插入的,适合用下界 Super。

2. 为什么要用通配符和边界

<? extends T> 和 <? super T> 是Java泛型中的“通配符(Wildcards)” 和 “边界(Bounds)”的概念 - <? extends T> 是指 “**上界通配符(Upper Bounds Wildcards)**” - <? super T> 是指 “**下界通配符(Lower Bounds Wildcards)**” 使用泛型的过程中,经常出现一种很别扭的情况。比如,我们有*Animal*类,和它的派生类*Pig*类。 ```java public class Animal { } class Pig extends Animal { } ``` 然后有一个最简单的容器:Fence类。栅栏里可以放一个泛型的“东西”。我们可以对这个东西做最简单的“放”和“取”的动作:set()和get()方法。 ```java class Fence { private T item; public Fence(T item) { this.item = item; } public T getItem() { return item; } public void setItem(T item) { this.item = item; } } ``` 现在我定义一个“围栏”,逻辑上水果盘子当然可以住Pig。 ```java public static void main(String[] args) { Fence pigFence = new Fence(new Pig()); } ``` ![image-20220328220710117](https://img-blog.csdnimg.cn/img_convert/c4ef93c6a4b4664c99b901f4576f08a8.png) **编译器脑袋里认定的逻辑是** - Pig is-a Animal - 围栏 是一个装Animal的围栏, 不是一个装Pig的围栏 总结就是, 容器中的东西有继承关系,容器没有继承关系, 为了让泛型用起来更加舒服, Sun就提出了<? extends T>和<? super T>的方法, 来让容器和内容发生点关系 # 3. 什么事上界 上界通配符(Upper Bounds Wildcards) ```java Fence <? extends Animal> ``` **可以方动物和动物派生的类, 什么动物都能放** ```java Fence<? extends Animal> fence = new Fence(new Pig()); ``` # 4. 什么是下界? 下界通配符(Lower Bounds Wildcards) ```java Fence <? super Animal> ``` **一个可以放Animal和以Animal为基类的Fence** # 5. 上下界通配符的副作用 边界让Java不同泛型之间的转换更容易了。但不要忘记,这样的转换也有一定的副作用。那就是容器的部分功能可能失效。 ## 1. 上界只能取 ```java Fence<? extends Animal> fence = new Fence(new Pig()); // fence.setItem(new Animal()); // 编译错误 // fence.setItem(new Pig()); // 编译错误 Animal item = fence.getItem(); // 编译成功 ``` ## 2. 下界只能存 ```java Fence<? super Animal> fence2 = new Fence(new Pig()); fence2.setItem(new Animal()); // 编译成功 fence2.setItem(new Pig()); // 编译成功 // Animal item2 = fence2.getItem(); // 编译错误 ``` ## 3. 为什么是上去下存, 有什么规律吗? 好难记忆 这个本质其实就是我们方法重写,是一样的 1、子类方法名必须与被覆盖方法名一致 2、子类方法访问修饰符权限必须等于或大于被覆盖方法的访问修饰符权限 3、子类方法的返回值类型必须与被覆盖方法返回值类型一致 4、子类方法抛出异常必须等于或小于被覆盖方法所抛出的异常 5、子类方法的参数列表必须与被覆盖方法的参数列表一致 本质就是, 我的重写方法必须可以完全替换我们的父类方法, 所以修饰不能减小, 返回和异常不能变大, 不然我们的代码可能出现编译问题, 但是为什么入参必须一样呢, 原因有两个, 1. 那就变成了重载, 2. 如果入参变大可能编译不过(部分功能缺失) , 如果变小, 入参对象的方法的行为可能变了, 方法的逻辑可能出现变化, 导致bug甚至异常 **方法重写可以简单记作: 中间相等两边小, 使用范围不能小** ### 1. 类比上界问题 ```java Fence<? extends Animal> fence = new Fence<>(new Pig()); // fence.setItem(new Animal()); // 编译错误 // fence.setItem(new Pig()); // 编译错误 Biology item = fence.getItem(); // 编译成功 ``` Animal item = fence.getItem(); 很显然我们的Animal 可以接住任何以<? extends Animal>为上界的实例, 所以没毛病 但是fence.setItem(new Pig()); 的编译错误好像就有点不好解释了? 应该这样理解, T作为一个泛型, 在实际编译后,只能是一个固定的值(擦除后都是Object, 定义上界擦除就是上界了), 反编译后的类 ```java Fence<? extends Animal> fence = new Fence(new Pig()); Biology item = (Biology)fence.getItem(); // 强转成Biology ``` 所以, 假设我们使用的T是一个Pig类型, 在setItem中就不能接收Animal了,所以不能存储, **范围小的T不能接收范围大的实例对象** ### 2. 类比下界问题 ```java Fence<? super Animal> fence2 = new Fence<>(new Pig()); fence2.setItem(new Animal()); // 编译成功 fence2.setItem(new Pig()); // 编译成功 // fence2.setItem(new Biology()); // 编译错误 // Animal item2 = fence2.getItem(); // 编译错误 Object item2 = fence2.getItem(); // 编译成功 ``` 其实就和上面差不多, 无论下界T是哪个类型, 总是可以接收下界一下的任何实例 ,不能接收下界以上的实例 Animal item2 = fence2.getItem(); // 编译错误, 如果我们返回的类型是Biology,就无法接收 Object item2 = fence2.getItem(); // 编译成功, 但是总是可以使用Object那接收, 因为所有的类都是Object的派生类 总结发现: **我们可以保存的类永远在上界或者下界之下的类,** 上下界只是限制了set,get行为, 而不是扩大了可以存放的范围
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值