【用Java学习数据结构系列】泛型上界与通配符上界

 看到这句话的时候证明:此刻你我都在努力

加油陌生人

个人主页:Gu Gu Study
专栏:用Java学习数据结构系列
喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努力留下足迹

喜欢的话可以点个赞谢谢了。
作者:小闭


目录

前言

泛型的概念

泛型的擦除机制

泛型的上界

通配符上界

我们实现一下场景一:

场景二:

泛型上界与通配符上界的区别

泛型上界:

通配符上界:

通配符下界


前言

本系列准备已经结束,反射,lambda表达示,之类知识了。本系列属于数据结构初阶,进阶的敬请期待。本文章主要是讲泛型的进一步认识,以及更加底层的String类的认识。

之前也写过一篇泛型初阶的一篇文章,大家如果没看过可以再看看。

这篇文章已经讲了:包装类,简单的编译器推导,泛型的基本使用,以及泛型上界。

泛型的概念

泛型是Java中一种强大的特性,它允许程序员在编写代码时指定类型参数,从而使得代码更加灵活和可重用。泛型提供了一种方式,使得编译器可以在编译时检查类型安全,避免了类型转换的错误和运行时的类型检查。

通俗来说: 就是适用于许多许多类型 ,从代码上讲,就是对类型实现了参数化。

语法:

class 泛型类名称<类型形参列表> {

// 这里可以使用类型参数

}

简单示例泛型的简单使用:

class MyArray<T> {  //注释1
    public Object[] array = new Object[10];
    public T getPos(int pos) {
        return (T)this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();//注释2
        myArray.setVal(0,10);
        myArray.setVal(1,12);//注释3
        int ret = myArray.getPos(1);
        System.out.println(ret);
        myArray.setVal(2,"bit");//注释4 此处是错误的
    }
}

代码注释处解析:

注释一:我们在类名后加了<T>,这里的作用就是泛型得基本用法,相当于这个T就代表一个类,但具体是哪个类我,还需要在创建这个类对象的时候,我们指定哪个类,才会知道。

注释二:这里我们创建对象时(new一个对象时)我们同样在类名后加了<Integer>,这就是我们指定T就是Integer类,则在我们创建的类对象时 T 就是Integer。

注释三:因为我们指定T为Intege类型,则在使用setVal(1,12);方法时我们可以直接传参12,然后jJVM就会进行自动拆包了。

注释四:首先说明这里的使用是编译器是会报错的,因为我们前面已经指定T为Integer了,这是传入一个String显然是不对的。所以编译器是会报错的。要想传入String储存到数组中,我们就需要在创建一个MyArray<String>对象。


泛型的擦除机制

关于泛型的擦除机制,它是Java泛型实现的一个核心概念。在Java中,泛型的类型参数在编译期间会被替换为其边界或Object类型,这个过程被称为类型擦除。这意味着在运行时,泛型的类型参数实际上是被“擦除”了,泛型代码在运行时无需知晓具体的类型参数。例如,如果有一个泛型类`List<T>`,在运行时,无论`T`是什么类型,`List<T>`都会被当作`List<Object>`来处理。

这种机制的主要目的是为了向后兼容Java的旧版本,同时减少代码重复,使得代码更加简洁。但是,这也带来了一些限制和挑战,比如不能在运行时获取泛型参数的具体类型,泛型数组的创建受到限制等。

尽管如此,通过一些技巧和设计模式,可以在一定程度上绕过这些限制,让代码更加灵活和可扩展。

类型擦除机制也意味着,在编译过程中,所有的泛型类型参数`T`都会被替换为`Object`,这就是我们通常所说的泛型擦除。由于被编译器当作`Object`类型处理,我们可以通过反射set任意类型的参数。但是,这种擦除也导致了一些问题,比如在泛型类中不能直接调用泛型参数的具体方法,因为这在编译时是未知的。解决这个问题的一种方法是给泛型参数一个边界,这样编译器就能知道泛型参数至少具有哪些方法。

总结:泛型的擦除机制是Java泛型实现的关键部分,它允许泛型代码在运行时以一种类型安全的方式处理不确定的类型,但同时也带来了一些限制和挑战。


泛型的上界

什么是泛型上界呢?

泛型上界(Bounded Type Parameters)是泛型编程中的一个概念,它允许我们为泛型类型参数指定一个边界,即限制泛型参数必须是某个类或接口的子类或实现。这样做可以提供更多的类型安全,并允许在泛型代码中使用更具体的操作。

在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。 就如上面所说给泛型一个边界,这样编译器就能知道泛型参数至少具有哪些方法。

代码语法:

class 类名 <形参类型 extends 另一个类>{
//代码
}

例如:

public class Myarr< E extends Number >{
//代码
}

代码解析:

这里当我们要创建Myarr这个类对象时,指定 E 的类型时,那么这时我们指定的类只能是Number类或它的子类作为类型实参。如果不是那么编译器就会报错。

扩展:那么我们如果我们没有定义上界,而是public class Myarr< E >时,我们就可以看做E extends Object;

通配符上界

我们上面说过当我们使用泛型类时吗,我们需要指定一个类作为泛型参数类。那么这时我们就会产生两个场景。

场景一:

那么如果我们有一个方法,是获取各个泛型类对象里的元素,这时我们定义这个方法时的形参类型到底怎么确定呢?如果我们指定一个类就会使得形参类型定死,无法实现获取各种泛型类里面的元素。

场景二:

如果我们现在要创建一个泛型类,但是暂时还不想示例化,只是定义一个null的泛型类,后面才随机实例化范围内的泛型类对象。

为了实现上面两个场景,就有了通配符上界

class Food {
    public void show(){
        System.out.println("食物");
    }
}
class Fruit extends Food {
    public void show(){
        System.out.println("水果");
    }

}
class Apple extends Fruit {
    public void show(){
        System.out.println("苹果");
    }
}
class Banana extends Fruit {
    public void show(){
        System.out.println("香蕉");
    }
}

class Message<T> { // 设置泛型
    private T message ;
    public T getMessage() {
        return message;
    }
    public void setMessage(T message) {
        this.message = message;
    }
}


如上当我们有以上的类时。

我们实现一下场景一:

class TestDemo {
    public static void main(String[] args) {
        Message<? extends Fruit> message;//这时还不知道示例化苹果还是香蕉 
        
        message== new Message<Apple>() ;//过一会知道了,这时才指定类型,当然也可以是其它情况
        
        message.setMessage(new Apple());
        
       
       
    }
    
}

如上:我们在想创建对象时还没知到message对象中 T 为什么类(对象),那么这时我们就使用 通配符上界Message<? extends Fruit>进行暂时限定泛型类的范围。到了后面我们知道了,这里的知道可以是 if 判断得出,或是返回值判断知道,并不像代码中的一样是我们后面主观指定了Apple。

场景二:

class TestDemo {
    public static void main(String[] args) {
        Message<Apple> message = new Message<>() ;
        message.setMessage(new Apple());
        fun(message);
        Message<Banana> message2 = new Message<>() ;
        message2.setMessage(new Banana());
        fun(message2);
    }
    
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
        temp.getMessage().show();
        System.out.println(temp.getMessage());

    }
}

如上:我们的fun函数,我们可以根据不同的传值,就可以得出不同的对象(限定范围内的),只需要在实参里使用通配符上界的语法。

注意:因为我们这里Message的指定类型是Fruit的子类或其本身,所以这里我们可以是直接用Fruit来直接 接受这个指定的类型的,顶多也就是向上转型,但是我们是无法对temp进行设置类的,因为这时我们传入实参的时候已经确定了的,T已经确定了一个类,而我们这时也就无法对其进行设置。

简单来说: 通配符的上界,不能进行写入数据,只能进行读取数据。 这与通配符下界是完全相反的。下文还会给大家介绍通配符下界。

public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
    Fruit b = temp.getMessage();
    System.out.println(b);
}


泛型上界与通配符上界的区别

在Java中,泛型上界和通配符上界是两个不同的概念,它们在泛型编程中扮演着不同的角色。下面分别解释它们的含义和区别:

泛型上界:

泛型上界是在声明泛型类型时指定的,用来限制泛型类型参数的类型范围。

它通常用在泛型类的声明中,例如 class Box<T extends Number> 表示 T 必须是 Number 或其子类的类型。

泛型上界是静态的,即在编译时就已经确定的。

通配符上界:

通配符上界是在实例化泛型类或使用泛型方法时使用的,用来指定通配符的类型范围。

它通常用在泛型的实例化和传递参数时,例如 List<? extends Number> 表示这个列表可以包含 Number 类型及其所有子类型的元素。

通配符上界是动态的,即在运行时可以确定具体类型。

具体区别

  • 使用场景不同
    • 泛型上界是在定义泛型类或接口时使用的,用来限制类型参数的类型。
    • 通配符上界是在实例化泛型类或调用泛型方法时使用的,用来指定具体的类型范围。
  • 类型安全
    • 泛型上界提供了编译时的类型安全检查,确保类型参数不会超出指定的范围。
    • 通配符上界则提供了运行时的类型安全,允许在运行时确定具体的类型。
  • 协变与逆变
    • 泛型上界可以是协变的(extends),也可以是逆变的(super),这取决于泛型参数是用作输入还是输出。
    • 通配符上界通常是协变的,表示可以接收更具体的类型。
  • 类型擦除
    • 泛型上界在编译时会进行类型擦除,泛型类型参数会被替换为其上界。
    • 通配符上界在运行时不会进行类型擦除,它们用于保持泛型的灵活性。
  • 实例化
    • 泛型上界在定义泛型类时实例化,不需要显式指定。
    • 通配符上界在创建泛型实例时显式指定。

通俗来说:泛型上界是在定义泛型时用来限制类型参数的,而通配符上界是在实例化泛型时用来指定具体类型范围的。

通配符下界

通配符下届与上界相似,只是指定的范围有所不一样。还有就是跟上面说的与通配符的性质是相反的。

通配符的下界,只能进行写入数据,不能进行读取数据。

代码还是类似举例:

class Food {
    public void show(){
        System.out.println("食物");
    }
}
class Fruit extends Food {
    public void show(){
        System.out.println("水果");
    }

}
class Apple extends Fruit {
    public void show(){
        System.out.println("苹果");
    }
}
class Banana extends Fruit {
    public void show(){
        System.out.println("香蕉");
    }
}

class Plate<T> {
    private T plate ;
    
    public T getPlate() {
        return plate;
    }
    
    public void setPlate(T plate) {
        this.plate = plate;
    }
}

class TestDemo {
    public static void main(String[] args) {
        Plate<Fruit> plate1 = new Plate<>();
        plate1.setPlate(new Fruit());
        通配符的下界,不能进行读取数据,只能写入数据。
        fun(plate1);
        Plate<Food> plate2 = new Plate<>();
        plate2.setPlate(new Food());
        fun(plate2);
    }

    public static void fun(Plate<? super Fruit> temp) {
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
        temp.setPlate(new Apple());//这个是Fruit的子类
        temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
        System.out.println(temp.getPlate());//只能直接输出
    }
}

无法接受的原因:因为在实参传入进去时T也是被确定为Fruit的父类或Fruit类,无法确定返回的是哪个父类,所以我们无法接收。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908718.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ssm校园线上订餐系统的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码看文章最下面 需要定制看文章最下面 目 录 摘 要 I 目 录 III 第1章 绪论 1 1.1 研究背景 1 1.2目的和意义 1 1.3 论文研究内容 1 …

软设师知识点-计算机网络

计算机网络 在一台安装好TCP/IP协议的计算机上&#xff0c;当网络连接不可用时&#xff0c;为了测试编写好的网络程序&#xff0c;通常使用的目的主机IP地址127.0.0.1&#xff08;本地回送地址&#xff09; *网络设备 物理层的互传设备&#xff1a;中继器(用于扩展局域网网段…

【339】基于springboot的新能源充电系统

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;新能源充电系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解…

Spring Boot2(Spring Boot 的Web开发 springMVC 请求处理 参数绑定 常用注解 数据传递 文件上传)

SpringBoot的web开发 静态资源映射规则 总结&#xff1a;只要静态资源放在类路径下&#xff1a; called /static (or /public or /resources or //METAINF/resources 一启动服务器就能访问到静态资源文件 springboot只需要将图片放在 static 下 就可以被访问到了 总结&…

#Jest进阶知识:整合 webpack 综合练习

这一小节&#xff0c;我们来做一个综合的练习&#xff0c;该练习会整合&#xff1a; typescriptwebpackjest 准备工作 首先创建项目目录&#xff0c;通过 npm init -y 进行初始化。 整个项目我们打算使用 typescript 进行开发&#xff0c;因此需要安装 typescript npm i t…

可以将题库文档做成答题考试的小程序

&#x1f4a5;轻松构建个人题库&#xff0c;开启高效在线答题体验&#xff01;&#x1f4af; &#x1f389;梦想拥有个性化题库&#xff0c;随时随地进行在线练习吗&#xff1f;“土著刷题”小程序正是为此而生&#xff0c;助你实现愿望&#xff01;✨ &#x1f31f;这款小程序…

使用Netty实现一个简单的聊天服务器

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;Netty &#x1f96d;本文内容&a…

使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接

在 Web 开发中&#xff0c;WebSocket 为客户端和服务端之间提供了实时双向通信的能力。本篇博客介绍如何使用 Spring Boot 快速搭建一个 WebSocket 服务器&#xff0c;并支持多客户端的连接和消息广播。 1. WebSocket 简介 WebSocket 是 HTML5 的一种协议&#xff0c;提供了客…

PHP常量

PHP 中的常量是指一旦定义后将不能被改变的标识符。 常量可以用const和define&#xff08;&#xff09;来定义。 PHP常量的特性 不变性: 常量一旦定义&#xff0c;其值不能改变。全局作用域: 常量在定义后&#xff0c;可以在整个脚本的任何地方使用&#xff0c;无需使用 glo…

让Erupt框架支持.vue文件做自定义页面模版

Erupt是什么&#xff1f; Erupt 是一个低代码 全栈类 框架&#xff0c;它使用 Java 注解 动态生成页面以及增、删、改、查、权限控制等后台功能。 零前端代码、零 CURD、自动建表&#xff0c;仅需 一个类文件 简洁的注解配置&#xff0c;快速开发企业级 Admin 管理后台。 提…

Echarts 图表根据屏幕大小自适应图表大小/标签文字大小

自适应图表大小 echarts多个图表大小随屏幕的大小改变自适应&#xff0c;Echarts 多图表自适应窗口大小&#xff0c;echarts随页面大小变化而变化&#xff1b; 但 Echarts 同一页面存在多个图表的时候&#xff0c;只有一个生效 只有一个图表的时候 直接用 window.onresize …

基于 Transformer 的语言模型

基于 Transformer 的语言模型 Transformer 是一类基于注意力机制&#xff08;Attention&#xff09;的模块化构建的神经网络结构。给定一个序列&#xff0c;Transformer 将一定数量的历史状态和当前状态同时输入&#xff0c;然后进行加权相加。对历史状态和当前状态进行“通盘…

Docker:容器编排 Docker Compose

Docker&#xff1a;容器编排 Docker Compose docker-composedocker-compose.ymlservicesimagecommandenvironmentnetworksvolumesportshealthcheckdepends_on 命令docker compose updocker compose down其它 docker-compose 多数情况下&#xff0c;一个服务需要依赖多个服务&a…

力扣633.平方数之和 c++

给定一个非负整数 c &#xff0c;你要判断是否存在两个整数 a 和 b&#xff0c;使得 a2 b2 c 。 示例 1&#xff1a; 输入&#xff1a;c 5 输出&#xff1a;true 解释&#xff1a;1 * 1 2 * 2 5示例 2&#xff1a; 输入&#xff1a;c 3 输出&#xff1a;false提示&…

【ESP32】ESP-IDF开发 | I2C从机接收i2c_slave_receive函数的BUG导致程序崩溃解决(idf-v5.3.1版本)

1. 问题 在调试I2C外设的demo时&#xff0c;按照官方文档的描述调用相关API&#xff0c;烧录程序后发现程序会不断崩溃&#xff0c;系统log如下。 初步分析log&#xff0c;原因是访问到了不存在的地址。一开始我以为是自己的代码问题&#xff0c;反反复复改了几次都会出现同样的…

链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)

AB链表公共元素生成链表C 设A和B是两个单链表(带头节点)&#xff0c;其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C&#xff0c;要求不破坏A、B的节点 算法思想 表A&#xff0c;B都有序&#xff0c;可从第一个元素起依次比较A、B两表的元素&#xff0c;若元…

蓝牙BLE开发——红米手机无法搜索蓝牙设备?

解决 红米手机&#xff0c;无法搜索附近蓝牙设备 具体型号当时忘记查看了&#xff0c;如果你遇到有以下选项&#xff0c;记得打开~ 设置权限

2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能

基于matlab-GUI的脉冲响应不变法实现音频滤波功能&#xff0c;输入加噪信号&#xff0c;通过巴特沃斯模拟滤波器脉冲响应不变法进行降噪。效果较好。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a;2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能…

智慧生活新标准:解锁耐能科技的潜能

『从马路上&#xff0c;看到旁边摄影机下方的牌子写着科技执法』 『开车进入停车场时&#xff0c;车牌辨识成功开启闸门』 『回到家门口前&#xff0c;进行脸部辨识解锁开门』 『手机APP弹起提醒&#xff0c;出现宝宝的画面表示正在哭泣』 上述的情景&#xff0c;你我是否很熟悉…

[VUE]框架网页开发1 本地开发环境安装

前言 其实你不要看我的文章比较长&#xff0c;但是他就是很长&#xff01;步骤其实很简单&#xff0c;主要是为新手加了很多解释&#xff01; 步骤一&#xff1a;下载并安装 Node.js 访问 Node.js 官网&#xff1a; Node.js — Download Node.js 下载 Windows 64 位版本&…