当前位置: 首页 > news >正文

【java基础】类型擦除、桥方法、泛型代码和虚拟机

文章目录

  • 基础说明
  • 类型擦除
    • 无限定
    • 有限定
  • 转换泛型表达式
  • 方法类型擦除(桥方法)
    • 关于重载的一些说明
  • 总结

基础说明

虚拟机没有泛型类型对象一所有对象都属于普通类。在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为在1.0虚拟机上运行的类文件!
由于泛型是在1.5才引入的,为了兼容,在java文件编译后是肯定看不见泛型的。也就是类型擦除,下面就来介绍一下类型擦除


类型擦除

无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始
类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除(erased),并替换为其限
定类型(或者,对于无限定的变量则替换为Object)。


无限定

下面先来看下面的代码

public class MyTool<T> {private T info;public T getInfo() {return info;}public void setInfo(T info) {this.info = info;}
}

这就是很简单的一个泛型类。现在,我通过反射来查看T是什么类型。

    public static void main(String[] args) throws NoSuchMethodException {// 得到getInfo方法Method getInfo = MyTool.class.getDeclaredMethod("getInfo");System.out.println("getInfo返回值类型为:"+getInfo.getReturnType().getName());}

上面运行结果如下
在这里插入图片描述
可以发现如果没有指定泛型,那么在编译过后T被替换为了Object
即使我们指定了泛型,T还是会被替换为Object

    public static void main(String[] args) throws NoSuchMethodException {MyTool<Comparable> myTool = new MyTool<>();// 得到getInfo方法Method getInfo = myTool.getClass().getDeclaredMethod("getInfo");System.out.println("getInfo返回值类型为:" + getInfo.getReturnType().getName());}

在这里插入图片描述


有限定

上面的泛型类没有限制,下面来看一下有限定的情况

public class Tool<T extends Serializable> {private T info;public T getInfo() {return info;}public void setInfo(T info) {this.info = info;}
}

还是使用反射来查看T类型

        // 得到getInfo方法Method getInfo = Tool.class.getDeclaredMethod("getInfo");System.out.println("getInfo返回值类型为:"+getInfo.getReturnType().getName());

运行结果如下

在这里插入图片描述

可以发现限定符替换了T。
上面是一个限定符的,如果有两个或者多个限定符呢

public class MulTool<T extends Comparator & Comparable & Serializable> {public T t;public T getInfo() {return t;}
}

还是使用上面的反射代码,输出如下

在这里插入图片描述

可以发现返回的是Comparator,下面来交换一下限定的位置,分别让Comparable 和Serializable成为第一个(自己交换即可)。交换后代码运行结果如下

在这里插入图片描述
在这里插入图片描述

通过上面的运行结果,我们就可以得出结论,使用了类型限定符,那么第一个限定就会替换T


转换泛型表达式

还是上利用MyTool代码举例

public class MyTool<T> {private T info;public T getInfo() {return info;}public void setInfo(T info) {this.info = info;}
}

我们经过上面的学习,知道会进行类型擦除,上面的MyTool的T会被替换为Object。那么getInfo返回值就是Object的类型,但是我们在实际调用getInfo方法时只要传入了类型,那么返回值就是我们传入的类型。看下面代码

    public static void main(String[] args) {MyTool<String> stringMyTool = new MyTool<>();stringMyTool.setInfo("xxx");// 得到所有方法Method[] declaredMethods = stringMyTool.getClass().getDeclaredMethods();for (Method declaredMethod : declaredMethods) {String methodName = declaredMethod.getName(); // 方法名String returnType = declaredMethod.getReturnType().getName(); // 返回类型System.out.println("方法名:" + methodName + "--返回类型:" + returnType);}// 返回的类型为StringString info = stringMyTool.getInfo();}

运行结果如下

在这里插入图片描述

可以发现getInfo返回值确实为Object,但是我们 String info = stringMyTool.getInfo(); 这条语句并没有进行强转,这就说明编译器已经帮我们进行了强转。其实在调用stringMyTool.getInfo()编译器将其转换为了2条虚拟机指令

  • 对于MyTool.getInfo()的调用
  • 将返回值Object强转为String

上面是对方法返回值进行强转,其实对字段的访问也是一样的,如果将info字段修饰符改为public,也可以直接使用String进行接收

        String filed = stringMyTool.info;

方法类型擦除(桥方法)

我们不说啥理论,直接看下面代码

public class Animal<T> {public void setX(T t) {}
}

这个一个泛型类,有一个set方法

public class Cat extends Animal<String> {@Overridepublic void setX(String s) {}
}

这是Cat类,继承了Animal类,指定了泛型为String,并且重写了setX方法。

下面就是使用Cat

        Animal<String> animal = new Cat();animal.setX("hello world!!!");

大家看看这个代码,有没有发现问题?我们使用Animal来接收了一个Cat对象,这是正确的。但是animal.serX就不怎么对劲了。下面我来分析一下

  • 由于Animal会发生类型擦除,所以animal.setX实际会调用 Animal.setX(Object)
  • 由于animal引用的是一个Cat,所以会去寻找Cat.setX(Object)
  • 问题出现了,Cat根本没有setX(Object),只有setX(String)

可以发现,类型擦除和多态产生了冲突。为了解决这个问题,编译器会在Cat类中生成一个桥方法。在Cat中生成的桥方法如下

    public void setX(Object s){setX((String) s);}

其实就是生成了一个参数为Object类型的setX方法,这个方法会去调用参数为String类型的方法,就好像桥梁的作用一样,所以我们成为桥方法。

为了验证上面的说法,也就是编译器会给我们的代码生成一个桥方法,下面我就使用反射输出Cat的所有方法。

    public static void main(String[] args) {// 得到所有方法Method[] declaredMethods = Cat.class.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {String methodName = declaredMethod.getName(); // 方法名// 参数类型集合List<String> types = Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getTypeName).collect(Collectors.toList());System.out.println("方法名:" + methodName + "--参数类型:" + types);}}

上面的代码输出如下

在这里插入图片描述

可以发现编译器确实给我们生成了一个setX方法,参数类型就是Object,这个方法就是一个桥方法。有了这个桥方法,多态和类型擦除的问题也就解决了。


关于重载的一些说明

通过上面的例子,大家应该对桥方法有了清晰的认识,有些思想活跃的人可能就会觉得不太对劲了。大家回想一下重载的定义,重载就是参数名相同,参数不同。

这确实没问题,下面我在Animal定义应该getT方法,然后在Cat里面重写这个方法

public class Animal<T> {private T t;public void setX(T t) {}public T getT() {return t;}
}
public class Cat extends Animal<String> {@Overridepublic void setX(String s) {}@Overridepublic String getT() {return "";}
}

根据上面的桥方法,大家想一下,是不是在Cat里面会生成应该 public Object getT()方法呢?我们还是通过的反射代码查看,代码和运行结果如下

    public static void main(String[] args) {// 得到所有方法Method[] declaredMethods = Cat.class.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {String methodName = declaredMethod.getName(); // 方法名// 参数类型集合List<String> types = Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getTypeName).collect(Collectors.toList());// 得到返回类型String returnType = declaredMethod.getReturnType().getName();System.out.println("方法名:" + methodName + "\t\t参数类型:" + types + "\t\t返回类型:" + returnType);}}

在这里插入图片描述

可以发现,在Cat里面存在了2个同名的方法,并且参数相同,这已经违法了重载的定义,按理说程序应该直接报错,但是并没有,原因就是在虚拟机中,会由参数类型和返回类型共同指定一个方法,上面代码中参数为Object的getT方法就是一个桥方法。


总结

在最后,对于java泛型的转换,我们需要记住以下几点

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都会替换为它们的限定类型
  • 会通过合成桥方法来保持多态
  • 为保持类型安全性,必要时会插入强制类型转换

关于泛型的更多知识,参考以下内容

泛型程序设计基础
类型擦除、桥方法、泛型代码和虚拟机
泛型的限制及其继承规则
泛型的通配符(extends,super,?)

相关文章:

【java基础】类型擦除、桥方法、泛型代码和虚拟机

文章目录基础说明类型擦除无限定有限定转换泛型表达式方法类型擦除&#xff08;桥方法&#xff09;关于重载的一些说明总结基础说明 虚拟机没有泛型类型对象一所有对象都属于普通类。在泛型实现的早期版本中&#xff0c;甚至能够将使用泛型的程序编译为在1.0虚拟机上运行的类文…...

十家公司有九家问过的软件测试面试题,最后一题我猜你肯定不会

最近面试了一些测试方面相关的岗位&#xff0c;通过牛客等途径也看了不少的面经&#xff0c;发现大部分人面试题目都有很多相似点&#xff0c;结合自己的一些面试经历&#xff0c;现在分享一些我面试中碰到过的问题 常见的面试题 1、jmeter的加密参数如何入参&#xff1f; 2…...

C++核心知识(三)—— 静态成员(变量、函数、const成员)、面向对象模型(this指针、常函数、常对象)、友元、数组类、单例模式

【上一篇】C核心知识&#xff08;二&#xff09;—— 类和对象(类的封装)、对象的构造和析构(浅拷贝、深拷贝、explicit、动态分配内存)1. 静态成员在类定义中&#xff0c;它的成员&#xff08;包括成员变量和成员函数&#xff09;&#xff0c;这些成员可以用关键字static声明为…...

RocketMQ【3】Rocketmq集群部署(多master多slave)异步复制

系列文章目录 RocketMQ【1】linux安装配置Rocketmq&#xff08;单机版&#xff09; RocketMQ【2】Rocketmq控制台安装启动&#xff08;单机版&#xff09; 文章目录系列文章目录一、异步复制的优缺点1、优点2、缺点二、架构1、架构图2、介绍3、机器配置三、配置1、master节点配…...

魏玛早春 木心

<font face“黑体” color#CD5C5C size6 魏玛早春 木心 温带每个季节之初 总有神圣气象恬漠地 剀切地透露在风中 冬天行将退尽 春寒嫩生生 料峭而滋润 漾起离合纷纷的私淑记忆 日复一日 默认季节的更替 以春的正式最为谨慎隆重 如果骤尔明暖 鸟雀疏狂飞鸣 必定会吝悔似的剧…...

关于Scipy的概念和使用方法及实战

关于scipy的概念和使用方法 什么是Scipy Scipy是一个基于Python的科学计算库&#xff0c;它提供了许多用于数学、科学、工程和技术计算的工具和函数。Scipy的名称是“Scientific Python”的缩写。 Scipy包含了许多子模块&#xff0c;其中一些主要的子模块包括&#xff1a; …...

第二章Linux操作语法1

文章目录vi和vim常用的三种模式vi和vim快捷键Linux开机&#xff0c;重启用户管理用户信息查询管理who和whoami用户组信息查询管理用户和组的相关文件实用指令集合运行级别帮助指令manhelp文件管理类pwd命令ls命令cd命令mkdir命令rmdir命令rm命令touch命令cp指令mv指令文件查看类…...

linux内核调度问题分析

目录 一、调度场景分析 不支持内核抢占的内核 支持内核抢占 二、如何让新进程执行 三、调度的本质 一、调度场景分析 假如内核只有3个线程&#xff0c;线程0创建线程1和线程2.当系统时钟到来时&#xff0c;时钟中断处理函数会检查是否有进程需要调度。当有进程需要调度时…...

C语言-基础了解-25-C强制类型转换

C强制类型转换 一、强制类型转换 强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为…...

【Python】如何安装 Allure 工具进行自动化测试

Allure 是一种流行的工具&#xff0c;用于以人类可读的格式生成测试报告&#xff0c;从而更容易理解和分析测试结果。在这篇博客中&#xff0c;我们将探索如何在 Windows 机器上安装 Allure 及其依赖项。 1 Prerequisites 先决条件 在田辛老师开始之前&#xff0c;请确保您的…...

nginx七大核心应用场景详解 解决生产中的实际问题 二次开发扩展

nginx七大核心应用场景详解 & 解决生产中的实际问题1、nginx的安装与简单配置1.1、环境准备1.2、nginx基本操作指令&#xff1a;1.3、安装成系统服务1.4、conf 配置文件说明2、虚拟主机2.1、nginx多主机配置2.2、二级域名与短网址解析3、基于反向代理的负载均衡3.1、跳转到…...

Java 整合 Redis

文章目录Java 整合 Redis一、Jedis二、Spring-Data-RedisJava 整合 Redis Redis 在项目中我们主要用来做缓存&#xff0c;就像我们用 MySQL 做持久层数据一样。Redis 的客户端有两种实现方式&#xff1a; 一是直接调用 Jedis 来实现&#xff0c;类似于 Java 使用 JDBC 操作数…...

Django实践-03模型-02基于admin管理表

文章目录Django实践-03模型利用Django后台管理模型1. 将admin应用所需的表迁移到数据库中。2. 创建访问admin应用的超级用户账号&#xff0c;3. 运行项目4.注册模型类5.对模型进行CRUD操作。6.实现学科页和老师页效果1. 修改polls/views.py文件。2.修改templates/polls/subject…...

如何安装python

windows安装 下载安装包 登录python官网 https://www.python.org/ 点击downloads 置顶下载的是最新的python版本 如果想下载指定版本往下翻找 安装程序 点击即可下载&#xff0c;然后打开下载的exe程序 勾选添加pythonexec到path&#xff0c;也就是添加到环境变量 使用a…...

java String类 万字详解(通俗易懂)

目录 一、前言 二、介绍和溯源 三、String类常用构造器 1.String() 2.String(byte[] bytes) 3.String(char[] value) 4.String(char[] value, int offset, int count) 5.String(String original) Δ演示 : 四、不同方式创建String类对象的区别 1.直接赋值的方式 2.常规new…...

Hive拉链表

概述 拉链表&#xff1a;维护历史状态以及最新状态数据的表 作用场景 1. 数据量比较大。 2. 表中的部分字段会被更新&#xff0c;比如用户的地址&#xff0c;银行利率&#xff0c;订单的状态等。 3. 需要查看某一个时间点或者时间段的历史快照信息&#xff0c;比如&#xff0c;…...

day1 开发我的第一个MyBatis程序

文章目录开发我的第一个MyBatis程序1. resources目录&#xff1a;2. 开发步骤3. 从 XML 中构建 SqlSessionFactoryMyBatisIntroductionTest4. mybatis中有两个主要的配置文件&#xff1a;5. 关于第一个程序的小细节mybatis-config.xml6. 关于mybatis的事务管理机制。&#xff0…...

【CDP】更改solr 存储路径导致ranger-audit 大量报错问题解决

前言 我们生产上公司是使用的CDP集群&#xff0c;一次管理员通知&#xff0c;Solr 组件的数据存放路径磁盘空间不够。 我们的solr 组件时为 Ranger 服务提供日志审计功能&#xff0c; 在我们更改了磁盘路径&#xff0c;并重启了Solr 组件&#xff0c;然后发现相关组件&#…...

JavaScript基础一、简介

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…...

Qt音视频开发20-vlc内核动态保存录像文件(不需要重新编译源码)

一、前言 在vlc默认提供的保存文件方式中&#xff0c;通过打开的时候传入指定的参数来保存文件&#xff0c;直到关闭播放生成文件&#xff0c;这种方式简单暴力&#xff0c;但是不适用大部分的场景&#xff0c;大部分时候需要的是提供开始录制和停止录制的功能&#xff0c;也就…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

Python 包管理器 uv 介绍

Python 包管理器 uv 全面介绍 uv 是由 Astral&#xff08;热门工具 Ruff 的开发者&#xff09;推出的下一代高性能 Python 包管理器和构建工具&#xff0c;用 Rust 编写。它旨在解决传统工具&#xff08;如 pip、virtualenv、pip-tools&#xff09;的性能瓶颈&#xff0c;同时…...

【JVM面试篇】高频八股汇总——类加载和类加载器

目录 1. 讲一下类加载过程&#xff1f; 2. Java创建对象的过程&#xff1f; 3. 对象的生命周期&#xff1f; 4. 类加载器有哪些&#xff1f; 5. 双亲委派模型的作用&#xff08;好处&#xff09;&#xff1f; 6. 讲一下类的加载和双亲委派原则&#xff1f; 7. 双亲委派模…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...