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

news2024/11/24 15:01:52

文章目录

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

基础说明

虚拟机没有泛型类型对象一所有对象都属于普通类。在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为在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);
        }
        // 返回的类型为String
        String 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> {

    @Override
    public 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> {

    @Override
    public void setX(String s) {

    }

    @Override
    public 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,?)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

L - Let‘s Swap(哈希 + 规律)

2023河南省赛组队训练赛&#xff08;四&#xff09; - Virtual Judge (vjudge.net) 约瑟夫最近开发了一款名为Pandote的编辑软件&#xff0c;现在他正在测试&#xff0c;以确保它能正常工作&#xff0c;否则&#xff0c;他可能会被解雇!Joseph通过实现对Pandote上字符串的复制和…

文件上传和下载(原生JS + SpringBoot实现)

目录 概述 前端编写-上传表单和图片回显 HTML表单代码 发送请求逻辑 CSS代码 后端编写-文件上传接口 后端编写-文件下载接口 概述 在现代Web应用程序中&#xff0c;文件上传和下载是常见的功能。本博客将介绍如何使用原生JS和Spring Boot实现文件上传和下载的功能。 在其…

vue移动端h5,文本溢出显示省略号,且展示‘更多’按钮

问题&#xff1a; 元素宽度100%&#xff0c;宽度会随着浏览器缩放而变化。元素内文本超过4行时显示省略号&#xff0c;同时展示‘更多’按钮&#xff0c;点击更多按钮展示全部文本。如下图所示 超出四行显示省略号(…)的代码 .content{overflow:hidden;text-overflow: elli…

【Spring】入门概述(一)

&#x1f697;Spring学习第一站~ &#x1f6a9;本文已收录至专栏&#xff1a;Spring家族学习之旅 &#x1f44d;希望您能有所收获 一.初识 Spring并不是单一的一个技术&#xff0c;而是一个大家族&#xff0c;发展到今天已经形成了一种开发的生态圈&#xff0c;Spring提供了若…

JavaScript(2)

一、事件 HTML事件是发生在hTML元素上的“事情”。比如&#xff1a;按钮被点击、鼠标移动到元素上等… 事件绑定 方式一&#xff1a;通过HTML标签中的事件属性进行绑定 <input type"button" value"点我" onclick"on()"><script>fun…

C++基础 | 从C到C++快速过渡

一、开发环境 c使用的编译器是g。 vim或者vscodeclionVS 二、C版本的Hello World /*** brief c版本helloworld示例* author Mculover666* date 2023/2/26*/#include <iostream> using namespace std;int main() {int a 1;double b 3.14;char c[] "str…

软件测试用例篇(4)

测试知识回顾: 我们想要根据需求来写测试用例&#xff0c;首先要保证的需求的合理性和正确性&#xff0c;首先要验证需求&#xff0c;需求合理&#xff0c;理解需求&#xff0c;细化需求&#xff0c;把大需求细化成小需求&#xff0c;根据每一个小需求提炼出功能点根据每一个功…

html+css 实现 熊猫样式

效果 html代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compatible"…

【MySQL高级篇】第01章 Linux下MySQL的安装与使用

第01章 Linux下MySQL的安装与使用 1. 安装前说明 1.1 查看是否安装过MySQL 如果你是用rpm安装, 检查一下RPM PACKAGE&#xff1a; rpm -qa | grep -i mysql # -i 忽略大小写检查mysql service&#xff1a; systemctl status mysqld.service1.2 MySQL的卸载 1. 关闭 mysql …

工控机ARM工业边缘计算机搭建Node-Red环境

搭建Node-Red环境Node-RED是一个基于Node.js的开源可视化流程编程环境&#xff0c;可以轻松构建自定义应用程序&#xff0c;通过连接简单的节点来完成复杂的任务。Node-RED提供了一种简单的方法&#xff0c;可以快速连接到外部服务&#xff0c;从而实现物联网应用的开发。Node-…

乡村企业门户网站

技术&#xff1a;Java、JSP等摘要&#xff1a;随着时代的发展&#xff0c;电脑与Internet已经进入我们的生活。信息时代的来临&#xff0c;知识经济的扩张&#xff0c;网站已越来越靠近我们的生活。据CNNIC报告显示&#xff0c;中国上网用户有6800万。通过Internet来经营运作一…

寻找时空中的引力波:科学家控制量子运动至量子基态

据英国每日邮报报道&#xff0c;时空织布里的涟漪或可以揭示宇宙在140亿年前是如何产生的&#xff0c;然而寻找这些名为“引力波”的涟漪却一直难以捉摸。现在美国科学家们声称他们发现了改善用于检测宇宙大爆炸的引力波的探测器的方法。 ​宇宙大爆炸残留的引力波 美国加州理…

电脑文件软件搬家迁移十大工具

10 大适用于 Windows 的数据迁移软件。 数据迁移至关重要&#xff0c;几乎所有组织都依赖于此。如果您认为数据传输不是一件容易的事&#xff0c;那么数据迁移软件可以帮上忙。 1、奇客电脑迁移 将现有操作系统、软件、文件迁移到 新电脑的最佳方法之一是使用名为奇客电脑迁移…

SpringMvc快速启动

Spring快速启动 1、tomcat配置 仔细查看下图标记位置配置 添加Tomcat server时选择LocalApplcation server选择Http port与JMX port 2、Project Structure 打开FIle -> Project Structure&#xff0c; 确认WEB-INFO下是否添加了lib&#xff0c;并将.jar包加入lib包中 …

mysql 主从复制 双主双从

master4: 配置 ./etc/my.cnf #主服务器唯一Id server-id4 #启用二进制日志[必填] #log-bin自己mysql的路径/mysqlbin #主机&#xff0c;0:读写,1:只读 read-only0 #忽略不需要同步的数据库 #binlog-ignore-dbmysql #需要同步的数据库:数据库名 binlog-do-dbtest #作为从库…

糖化学类854262-01-4,Propargyl α-D-Mannopyranoside,炔丙基 α-D-吡喃甘露糖苷

外观以及性质&#xff1a;Propargyl α-D-Mannopyranoside一般为白色粉末状&#xff0c;糖化学类产品比较多&#xff0c;一般包括&#xff1a;葡萄糖衍生物、葡萄糖醛酸衍生物&#xff0c;氨基甘露糖衍生物、半乳糖衍生物、氨基半乳糖衍生物、核糖衍生物、阿拉伯糖衍生物、唾液…

童流感诊治最新共识,专家全面解读

流感高发季节已经到来&#xff0c;孩子们接种疫苗吗&#xff1f;流感是人类面临的主要公共健康问题之一&#xff0c;儿童是流感的高危人群和严重病例的高危人群。近日&#xff0c;中华医学会呼吸病学年会第22届全国呼吸病学学术会议在福建厦门召开。会上&#xff0c;首都医科大…

Vue(1)-vue核心

1.Vue核心 【课程链接】 目录1.Vue核心1.1.Vue介绍1.1.1.官网1.1.2.介绍与描述1.1.3.Vue 的特点1.1.4.与其它 JS 框架的关联1.1.5.Vue 周边库1.2.初识vue1.3.Vue模板1.4.数据绑定1.5.el和data的两种写法1.6.MVVM模型1.7.数据代理1.7.1.回顾Object.defineProperty方法1.7.2.理解…

C++单例模式实现

目录 1.提出的需求 ​​​​​​​2.如何定义一个类&#xff0c;使得这个类最多只能创建一个对象&#xff1f; ​​​​​​​3.代码 ​​​​​​​4.小结 C/CLinux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 ​​​​​​​1.提出的需求 在架构设计时&am…

有什么比较好的bug管理工具?5款热门工具推荐

工具再优秀&#xff0c;适合自己才最重要。 为尽量讲透这个问题&#xff0c;本文的行文结构我先整理如下&#xff1a; 1、为什么需要bug管理工具&#xff1f; 2、好的bug管理工具的标准是什么&#xff1f; 3、好的bug管理工具推荐&#xff08;5款&#xff09; 4、如何挑选适合…