泛型笔记3-下界通配符

2023-10-11 21:10
文章标签 笔记 泛型 通配符 下界

本文主要是介绍泛型笔记3-下界通配符,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一节讨论了上界通配符的作用以及它的使用事项.今天将讨论下界通配符.

下界通配符

当我们需要动态地传入类对象及其超类类型的时候,由于擦除性质,编译器并不能确定所传入的对象是否是某一个对象的超类,而将它们都视作Object对象.当我们需要一个能使编译器识别这种关系的一种通配符,所以我们就有了下界通配符这一概念.
它的格式为:<? super 类名>.
它告诉编译器,你所要传入的参数类型只能是这个类及其超类类型.我们来看下面的一个关系图.
在这里插入图片描述
如果我们以水果为基类(作为super后面的类名),那么水果及其父类类型就是应当传入的对象类型.注意到它的水果的所有子类也应视作水果类型.那么在这里,仅有蔬菜及其子类类型不应当传入.
在这里插入图片描述

现在假设我们有一个盘子,对应一个容器类,如果这个盘子只能装水果,这其实对应到上界通配符.但我们不止想要它装水果,并且装植物的说法也是不对的,那么我们只能这么说,我们希望这个盘子不止能装水果,还要能装包含水果的任何门类.似乎这在现实中很诡异.因此我觉得下界通配符的真正意义不在于我需要这个盘子装什么,而是这个盘子不要装什么,所以自然一点的说法就是:我不希望这个盘子装蔬菜.
下界通配符给我们提供了一个手段,让我们能够排除一个基类的所有兄弟类型及其祖先的所有兄弟类型.接下来我们将通过一个例子展示这一点.
定义一个水果类,继承植物类.它具有如上图所示的若干子类,蔬菜类的略去不写.为了考虑它能否被传入一个容器类,这里可以不用定义它的成员变量和方法,虽然我这里确实写了,但其实大家完全可以忽略,定义一个空类型空构造器即可.如果说,想看一下容器类对象能否通过元素调用其成员方法,那么可以声明一个成员方法.

package 泛型演示;//声明一个水果类,以及它的一些子类
public class Fruit extends Plants{private String color;private double sweetness;private boolean isSmooth;public Fruit(String color, double sweetness, boolean isSmooth) {this.color = color;this.sweetness = sweetness;this.isSmooth = isSmooth;}public String getColor() {return color;}public void setColor(String color) {this.color = color;}public double getSweetness() {return sweetness;}public void setSweetness(double sweetness) {this.sweetness = sweetness;}public boolean isSmooth() {return isSmooth;}public void setSmooth(boolean smooth) {isSmooth = smooth;}
}class Strawberry extends Fruit{public Strawberry(String color, double sweetness, boolean isSmooth) {super(color, sweetness, isSmooth);}
}class Banana extends Fruit{public Banana(String color, double sweetness, boolean isSmooth) {super(color, sweetness, isSmooth);}
}class NorthBanana extends Banana{public NorthBanana(String color, double sweetness, boolean isSmooth) {super(color, sweetness, isSmooth);}
}class SouthBanana extends Banana{public SouthBanana(String color, double sweetness, boolean isSmooth){super(color, sweetness, isSmooth);}
}class XinjiangBanana extends NorthBanana{public XinjiangBanana(String color, double sweetness, boolean isSmooth) {super(color, sweetness, isSmooth);}
}class HainanBanana extends SouthBanana{public HainanBanana(String color, double sweetness, boolean isSmooth) {super(color, sweetness, isSmooth);}
}

在主方法中,如果我们以北方香蕉为基类,那么声明一个容器类对象,采用下界通配符修饰泛型,然后我们向里面加入一些对象,观察结果.
在这里插入图片描述
从图片中我们可以看到,除了北方香蕉和它的子类,添加其他类型的水果均会编译报错.这似乎很奇怪,难道它违反了下界通配符的原则,即它不能装入基类类型的父类型?事实上,这还是擦除的"锅".
sun公司的开发人员在java5开始引入泛型以后,希望今后的第三方库都将采用泛型来声明,构造,实现.但对于java5以前的类库,则不可避免地将面临重写的困境,但这需要一定的时间.不可能为了重写而让用户停止使用这些类库.开发人员采取了这样一个折中的办法,使得泛型能够跨类使用.直白地说,就是当过去的类和现在的泛型类库互相使用,过去的类将无法识别泛型,对它们来说仍然是没有规定类型约束的一些类,虽然这极大限制了泛型的能力,但保证了可维护性,称这样一种特性为泛型的擦除性质.
因为这个特性,编译器经常会陷入迷惑,当它不能明确你要传入一个什么类型的参数,为了安全,它不会允许你随意插入它.在这里,编译器通过下界通配符,知道了要允许基类及其父类传入容器,但不知道传入容器的对象的具体类型.这样,就无法为之分配内存,自然就不能执行代码.为什么允许插入子类型,我们说过,子类型也是父类型,即香蕉是一种水果是对的,水果却不一定是香蕉,当我们插入的是基类的子类型,编译器能识别出它是子类型,虽然由于擦除还是无法确切知道是哪种子类型,但它肯定都是基类类型,那么分配一个基类对象的内存空间就可以了.子类会完整继承父类的所有堆栈信息,所以不用担心会产生什么多余的变量或地址.
用下界通配符修饰的容器对象不可以直接调用get方法,这恰与上界通配符相反.除非你用Object类型的引用指向它,这也正是编译器的看法.但是这种做法没有意义.因为你还是无法知道它是一个什么类型对象,它的一切方法,包括继承过来的方法还是特有的方法都无法调用,除了Object对象的几个方法.它已经完全失去了一切身份信息.造成这种情况的原因是,执行get()方法时,编译器只知道获得的元素是基类类型或是它的超类类型,但并不知道应是哪种具体类型,不同于上界通配符的做法,它不能视之为基类类型而进行向下的类型转换,而进行向上的类型转换又无法确定这个类型是否真的是你获得的类型的超类类型,除非你一直向上转换成Object类型.这也就是为何只有用Object类型引用才能指向get()方法获得的对象的原因.
基于同样的考虑,如果不能获得数据,那么这个数据的存放也将失去意义.所以必须想个办法,使得我们使用了下界通配符存入的数据还能对我们可见.我们采用反射原理.调用Object的getClass()方法,就可以获得对象的具体类型.然后输出这个类型(这里我不清楚如何直接利用getClass的返回结果,所以只能先输出看看),了解到这个结果后,利用强制类型转换,就可以调用对象的特有方法或继承来的方法了.
请仔细观察下面的代码:

package 泛型演示;import java.util.ArrayList;
import java.util.List;public class GenericTest2 {public static void main(String[] args) {//2 采用下界通配符?super, 表示list中所有的元素都是NorthBanana类或其父类List<? super NorthBanana> list = new ArrayList<>();//2.1 下界通配符正确用法:可以使用add方法,但要符合泛型规定list.add(new XinjiangBanana("green", 0.3, true));list.add(new NorthBanana("green", 0.3, true));//2.2 下界通配符错误用法:添加XinjiangBanana的父类,无法通过编译
//        list.add(new Banana("green", 0.3, true));//2.3 因为不能保证返回的结果是哪一个父类,所以只能向上转型到最高类Object.
//        Fruit f1 = list.get(0);
//
//        NorthBanana nb1 = list.get(0);Object o1 = list.get(0);Object o2 = list.get(1);//        o1.getColor();//直接调用肯定不行,因为多态不允许父类对象调用子类特有的方法System.out.println(list.get(0).getClass());System.out.println(o1.equals(list.get(0)));System.out.println(list.get(1).getClass());Class cl = list.get(0).getClass();System.out.println(o2.equals(list.get(0)));XinjiangBanana xb = (XinjiangBanana)o1;System.out.println(xb.getColor());}
}

在这里插入图片描述

这篇关于泛型笔记3-下界通配符的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/190840

相关文章

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

论文阅读笔记: Segment Anything

文章目录 Segment Anything摘要引言任务模型数据引擎数据集负责任的人工智能 Segment Anything Model图像编码器提示编码器mask解码器解决歧义损失和训练 Segment Anything 论文地址: https://arxiv.org/abs/2304.02643 代码地址:https://github.com/facebookresear

数学建模笔记—— 非线性规划

数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中有一个或几个非线性函数的最优化问题的方法。运筹学的一个重要分支。2

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

查看提交历史 —— Git 学习笔记 11

查看提交历史 查看提交历史 不带任何选项的git log-p选项--stat 选项--pretty=oneline选项--pretty=format选项git log常用选项列表参考资料 在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的 工具是 git log 命令。 接下来的例子会用一个用于演示的 simplegit

记录每次更新到仓库 —— Git 学习笔记 10

记录每次更新到仓库 文章目录 文件的状态三个区域检查当前文件状态跟踪新文件取消跟踪(un-tracking)文件重新跟踪(re-tracking)文件暂存已修改文件忽略某些文件查看已暂存和未暂存的修改提交更新跳过暂存区删除文件移动文件参考资料 咱们接着很多天以前的 取得Git仓库 这篇文章继续说。 文件的状态 不管是通过哪种方法,现在我们已经有了一个仓库,并从这个仓

忽略某些文件 —— Git 学习笔记 05

忽略某些文件 忽略某些文件 通过.gitignore文件其他规则源如何选择规则源参考资料 对于某些文件,我们不希望把它们纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常它们都是些自动生成的文件,比如日志文件、编译过程中创建的临时文件等。 通过.gitignore文件 假设我们要忽略 lib.a 文件,那我们可以在 lib.a 所在目录下创建一个名为 .gi

取得 Git 仓库 —— Git 学习笔记 04

取得 Git 仓库 —— Git 学习笔记 04 我认为, Git 的学习分为两大块:一是工作区、索引、本地版本库之间的交互;二是本地版本库和远程版本库之间的交互。第一块是基础,第二块是难点。 下面,我们就围绕着第一部分内容来学习,先不考虑远程仓库,只考虑本地仓库。 怎样取得项目的 Git 仓库? 有两种取得 Git 项目仓库的方法。第一种是在本地创建一个新的仓库,第二种是把其他地方的某个