当前位置: 首页 > 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;也就…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

tauri项目,如何在rust端读取电脑环境变量

如果想在前端通过调用来获取环境变量的值&#xff0c;可以通过标准的依赖&#xff1a; std::env::var(name).ok() 想在前端通过调用来获取&#xff0c;可以写一个command函数&#xff1a; #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...