Java的类型擦除与泛型的关系

news2024/11/16 10:50:21

在讨论类型擦除之前,我们必须先来了解一下java的泛型。所谓的泛型就是参数化的类型。这就意思着我们可以具体的类型作为一个参数传递给方法、类、接口。

为什么我们需要泛型呢?首先我们都知道在java里,Object就是对象的父类。Object可以引用任何类型的对象。但是这一点会带来类型安全的问题。而泛型的出现就给java带来了类型安全这一项功能

  • 泛型方法:
class Test {
	static <T> void helloworld(T t){}
}
  • 泛型类
 
class Test<T> {
	T obj
	Test(T obj){
		this.obj = obj;
	}
}
  • 泛型接口
interface Test<T> {
	T getData();
}

使用泛型可以提供代码的复用,使用一份代码应用到不同的类型上。其次泛型还保证了类型的安全(在编译期就可以检查出来)。比如说:

ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");

// 编译器会阻止下面的操作,从而保证了我们的类型安全
list.add(100); // error

泛型还有一个好处,不需要单独的类型转换,如:

ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");


String s0 = (String)list.get(0); // 类型转换是不需要做的
// 下面展示了,我们不需要进行单独的类型转换
String s1 = list.get(0);
String s2 = list.get(1);

前面已经做好铺垫了,我们是时候聊类型擦除的问题了。

什么是类型擦除?所谓的类型擦除是一个只在编译期强制类型约束和在运行期丢弃类型信息的过程。如我们现在有这么一个泛型方法:

public static <T> boolean myequal(T t1, T t2){
	return t1.equals(t2);
}

编译器会用Object替换掉类型T,如下:

public static <Object> boolean myequal(Object t1, Object t2){
	return t1.equals(t2);
}

泛型类的类型擦除

在类级别的类型擦除遵循这样的规则:首先编译器丢弃类上的类型参数,并用它的第一个绑定类型替换它,如果类型参数没有绑定,就用Object来替换。

  • 参数类型没有绑定
public class MyClass<T> {
	private T[] elements;
	public void doSomething(T element){}
	public T getSomething(){}
}

MyClass的类型参数T没有绑定到任何类型,所以将会用Object来替换掉T,替换结果:

public class MyClass {
	private Object[] elements;
	public void doSomething(Object element){}
	public Object getSomething(){}
}
  • 参数类型有绑定
interface MyT {}
public class MyClass<T extends MyT> {
	private E[] elements;
	public void doSomething(T element){}
	public T getSomething(){}
}

MyTClass是MyClass的类型参数T第一个绑定到的类型,因此T将会被替换成MyTClass:

public class MyClass {
	private MyT[] elements;
	public void doSomething(MyT element){}
	public MyT getSomething(){}
}

为什么取第一个绑定就OK了呢?比如说,如果MyT还有父类,父类还有父类,那么我们的类型参数就有了很多间接的绑定,而第一个绑定就覆盖了所有的父类,因此用第一个绑定就可以了。

泛型方法的类型擦除

对于泛型方法,它的类型参数不会被存放起来,它遵循这样的规则:首先编译器丢弃方法上的类型参数,并用它的第一个绑定类型替换它,如果类型参数没有绑定,就用Object来替换。

  • 参数类型没有绑定
public static <T> void printSomething(T[] arr){
	for(T item: arr) {
		System.out.printf("%s", item);
	}
}

上面的方法,进行类型擦除的结果后:

public static void printSomething(Object[] arr){
	for(Object item: arr) {
		System.out.printf("%s", item);
	}
}
  • 参数类型有绑定
public static <T extends MyT> void printSomething(T[] arr){
	for(T item: arr) {
		System.out.printf("%s", item);
	}
}

上面的方法,进行类型擦除的结果后:

public static void printSomething(MyT[] arr){
	for(MyT item: arr) {
		System.out.printf("%s", item);
	}
}

类型擦除中产生的桥接方法

除了上述的规则外,对于那些相似的方法,编译器会创建一些合成方法来区分它们,这个合成方法是扩展相同的第一个绑定类的方法签名,这句话通过下面的例子就会有一个比较直观的认识。
首先我们有这么一个类:

class MyQueue<T> {
    private T[] elements;

    public MyQueue(int size){
        this.elements = (T[])new Object[size];
    }

    public void add(T data){}
    public T dequeue(){
        if(elements.length > 0){
            return elements[0];
        } else {
            return null;
        }
    }
}

上面这个类在类型擦除后,T都会被Object替换(具体规则请参考前面部分)。我们现在写一个类来继承MyQueue:

class IntegerQueue extends MyQueue<Integer> {

    public IntegerQueue(int size){
        super(size);
    }

    @Override
    public void add(Integer data) {
        super.add(data);
    }
}

接着我们写一个测试方法来引述出它的合成方法的原理:
请添加图片描述

我们可以看到MyQueue的add方法的参数类型已变成Object,所以queue.add("Helllo") 是说得过去。IDE也提示了这个类型可能有问题:请添加图片描述
其实,这也并不是我们想要的,因为IntegerQueue只想接收Int类型的。那么当我们运行这个测试用例时,我们就看到了下面的错误:请添加图片描述
这个例子再次说明泛型是为了类型安全而引入的功能。编译器是怎么做到的呢?它是怎么起作用的呢?

实际上,是编译器额外生成了一个方法(前面提到的合成方法)来做桥接。我们都知道,MyQueue进行类型擦除后,会变成下面这样:

class MyQueue {
    private Object[] elements;

    public MyQueue(int size){
        this.elements = (Object[])new Object[size];
    }
    public void add(Object data){}
    public Object dequeue(){
        if(elements.length > 0){
            return elements[0];
        } else {
            return null;
        }
    }
}

IntegerQueue 的方法public void add(Integer data){}MyQueue public void add(Object data){}是相似的,编译器就会为相似的方法创建一个中间的方法来做它们之间的桥。为了保证泛型的多态性在类型擦除,这个方法是生成在IntegerQueue ,而且编译器能够保证这种相似的方法不会匹配错,也就是编译都会这种相似的方法创建一个合成方法在它们之间做桥接。如何上面提到的,编译器创建的桥接方法如下:

 static class IntegerQueue extends MyQueue<Integer> {


        public void add(Object data){
            add((Integer) data);
        }
        
        public void add(Integer data) {
            super.add(data);
        }
    }

本例子中的这个合成方法就是

public void add(Object data){
       add((Integer) data);
}

它是扩展了相同的第一个绑定类的方法签名,换句话说就是它们的方法名是一样的,而这个合成方法的参数的类型是类型擦除后的第一个绑定类(具体可以参考前面部分的内容)。它的作用就是桥接了IntegerQueue类中的add方法与其父类中的add方法,以此来解决泛型在继承中的类型安全问题。

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

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

相关文章

收集两篇关于前端不错的文章

深以为然&#xff01; 为什么我建议前端框架优先选 Vue 而不是 React https://acejoy.com/2022/03/10/675/ 我两者都用过比较长的时间。网上各种“为什么我选React放弃了Vue”或者“为什么我选Vue放弃了React”之类的文章很多&#xff0c;实际都没什么用&#xff0c;必须要真…

Windows使用Paddle训练好的模型进行OpenVino推理引擎下的部署

目录一. Openvino下载二. 准备模型2.1 导出Paddle Inference模型2.2 转换为ONNX模型2.3 转换为ONNX模型2.3.1 获取部署代码2.3.2 环境准备2.3.3 编译一. Openvino下载 根据Paddle官方的描述&#xff0c;当前检测模型转换为openvino格式是有问题的&#xff0c;暂时只支持分割和…

docker部署常用服务器(redis,nginx,mysql,tomcat)

docker部署服务器docker部署redisdocker部署nginxdocker部署mysqldocker部署tomcatdocker部署redis 参考这篇博客&#xff0c;写的很详细 docker部署nginx 1.搜索镜像 docker search nginx 2.拉取镜像(不写版本默认拉取最新版) docker pull nginx 3.查看镜像是否拉取成功 docke…

Kafka-生产者基本使用

一、生产者原理 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。 在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator&#xff0c; Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka B…

Ae 案例:制作粒子空间穿梭动画

本文介绍使用 Ae 插件 Stardust 制作粒子空间穿梭动画的一般方法与步骤。示例视频1、新建合成。持续时间&#xff1a;10 秒。2、新建纯色图层&#xff0c;命名为“Stardust”&#xff0c;然后添加 Stardust 效果。3、再新建一个纯色图层&#xff0c;命名为“Mask”。使用矩形工…

php宝塔搭建部署实战易优宠物用品网站源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的易优宠物用品网站源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&#xff0c;宝…

SpringBoot集成Swagger,前后端接口文档解决方案

一个不断在迭代的项目&#xff0c;Controller层与POJO层肯定会是经常变动的&#xff0c;在目前前后端分离的大环境背景下有一份接口文档可以极大减少项目组成员之间的交流成本&#xff0c;也能支持自动化测试&#xff0c;但靠人工维护该文档总是不够稳妥&#xff0c;因此我们可…

23.1.21打卡 CF-1782D Many Perfect Squares

Problem - D - Codeforces 题外话: 痛苦的 C大模拟写不出D题数论我是真菜没想到, 泪目 -------------------------------------------------------------------------------------------------------------------------------- 先抛开这题, 我们先探究下平方数的规律 1 …

容器虚拟化技术Docker(三)DockerFile、Docker部署微服务、Docker-compose容器编排、Docker监控

容器虚拟化技术Docker&#xff08;三&#xff09;DockerFile、Docker部署微服务、Docker-compose容器编排、Docker监控 不熟悉的docker的可以参考&#xff1a; 容器虚拟化技术Docker&#xff08;一&#xff09;简介、安装、常见命令、数据卷、安装常规软件 容器虚拟化技术Do…

QSslSocket::supportsSsl()返回false问题解决

1.问题的提出今天研究Qt官方自带的有关QSslSocket类用法的例子。该例子存放在Qt安装目录下的Examples\Qt-XX.XX.XX\network\securesocketclient其中XX.XX.XX为Qt的版本号&#xff0c;如&#xff1a;5.14.1。在main函数QSslSocket::supportsSsl()返回false&#xff0c;如下&…

浅析RecyclerView预加载RV-Prefetch 机制

浅析RecyclerView预加载RV-Prefetch 机制 UI渲染基本流程&#xff08;UI-Thread,Render-Thread,SurfaceFlinger&#xff09;(硬件加速开启) 当系统V-Sync信号来临时&#xff0c;会唤醒主线程&#xff0c;回调编舞者Choreographer#FrameDisplayEventReceiver#onVsync()开始这一…

HPC Game小结

PART 1 - 基础知识 一、文件读取 a. 二进制文件 mmap https://stackoverflow.com/questions/44553907/mmap-sigbus-error-and-initializing-the-file fread fwrite //readFILE* fi;if(fi fopen("input.bin", "rb")){fread(&p, sizeof(int), 1, fi)…

JVM调优实战——jvm常用参数及方法

一、创建会内存溢出的程序 pom&#xff1a; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma…

Q_DISABLE_COPY、Q_DISABLE_MOVE、Q_DISABLE_COPY_MOVE用法详解及总结

1.前言在编程中&#xff0c;会用到某些资源&#xff0c;这些资源有的在整个应用程序期间是唯一的&#xff1b;是不能通过拷贝、赋值的方法存在多份的&#xff0c;如STL的std::unique_ptr指针指向的资源。现实中这样的资源有&#xff1a;文件指针、串口句柄等。试想如果存在多个…

TVM: End-to-End Optimization Stack for Deep Learning论文阅读

摘要 很多目前最为流行的深度学习框架&#xff0c;如 TensorFlow、MXNet、Caffe 和 PyTorch&#xff0c;支持在有限类型的服务器级 GPU 设备上获得加速&#xff0c;这种支持依赖于高度特化、供应商特定的 GPU 库。然而&#xff0c;专用深度学习加速器的种类越来越多&#xff0…

数据库系统概念 | 第四章:中级SQL

文章目录&#x1f4da; 连接表达式&#x1f407; 自然连接&#x1f407; 连接条件&#x1f955;natural条件&#x1f955;using 条件&#x1f955;on 条件&#x1f407; 内连接和外连接&#x1f955; 内连接inner join&#x1f955; 外连接outer join&#x1f343; 左外连接lef…

Web 应用渗透测试 00 - 信息收集

背景 这个系列写 Web 应用渗透测试相关的内容。此篇从信息收集开始&#xff0c;看一下 Web 应用端有哪些方面的信息值得渗透测试者去收集&#xff0c;能对后续的行动产生积极的影响。 Web 应用渗透测试 - 信息收集 security.txt 这个文件包含了网站的漏洞披露的联系方式。如…

Java面试题每日10问(18)

Miscellaneous Interview Questions 1. What are the advantages and disadvantages of object cloning? Advantage of Object Cloning You don’t need to write lengthy and repetitive codes. Just use an abstract class with a 4- or 5-line long clone() method.It is t…

二叉树的迭代遍历

二叉树的迭代遍历 前序遍历 基本思路 基本思路其实很简单, 使用递归遍历的时候, 一直是系统帮我们把其他数据压栈, 举个例子 > ans [5,4,6,2,1,null,null] 前序遍历的序列是: [5,4,2,1,6] , 栈的出入顺序是, 先入, 后出, 假如我们想要一个元素先出, 就要让它后入栈 基…

STC12驱动MLX90614红外测温模块在LCD1602显示

文章目录1、基本简介2、通信方式3、参考STC12例程参考文献1、基本简介 2、通信方式 通过芯片手册我们可以了解到这个模块的输出有PWM和SMBus方式&#xff0c;PWM长期做嵌入式开发的已经很熟悉了&#xff0c;那么什么是SMBus呢&#xff1f; SMBus&#xff08;系统管理总线&…