Java泛型的类型擦除示例详解
作者:宿宝臣 发布时间:2023-07-02 13:38:17
前言
Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。理解类型擦除对于用好泛型是很有帮助的,尤其是一些看起来“疑难杂症”的问题,弄明白了类型擦除也就迎刃而解了。
泛型的类型擦除原则是:
•消除类型参数声明,即删除<>及其包围的部分。
•根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
•为了保证类型安全,必要时插入强制类型转换代码。
•自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。
1 擦除类定义中的类型参数
1.1 无限制类型擦除
当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如<T>和<?>的类型参数都被替换为Object,参见1。
图 1: 擦除类定义中的类型参数
1.2 有限制类型擦除
当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如<T extends Number>和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object,参见2。
图 2: 擦除类定义中的有限制类型参数
2 擦除方法定义中的类型参数
擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例,见3。
图 3: 擦除泛型方法中的类型参数
3 桥接方法和泛型的多态
考虑下面的代码:
public interface Info<T> {
// just return var:-)
T info(T var);
}
public class BridgeMethodTest implements Info<Integer> {
@Override
public Integer info(Integer var) {
return var;
}
}
按照我们之前类型擦除的经验,在擦除类型后的代码应该是这个样子的:
public interface Info {
// just return var
Object info(Object var);
}
public class BridgeMethodTest implements Info {
@Override
public Integer info(Integer var) {
return var;
}
}
但是,明显可以看出,这样擦除类型后的代码在语法上是错误的:BridgeMethodTest类中虽然存在一个info方法,但是和Info接口要求覆盖的info方法不一致:参数类型不一致。在这种情况下,Java编译器会自动增加一个所谓的“桥接方法”(bridge method)来满足Java语法的要求,同时也保证了基于泛型的多态能够有效。我们反编译一下BridgeMethodTest.class文件可以看到Java编译器到底是如何做的:
$ javap BridgeMethodTest.class
Compiled from “BridgeMethodTest.java”
public class BridgeMethodTest implements Info<java.lang.Integer> {
public BridgeMethodTest();
public java.lang.Integer info(java.lang.Integer);
public java.lang.Object info(java.lang.Object);
}
可以看出,Java编译器在BridgeMethodTest中自动增加了两个方法:默认构造方法和参数为Object的info方法,参数为Object的info方法就是“桥接方法”。如何理解“桥接”二字呢?我们进一步反编译BridgeMethodTest看一下:
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
// Source File Name: BridgeMethodTest.java
public class BridgeMethodTest
implements Info
{
public BridgeMethodTest()
{
}
public Integer info(Integer integer)
{
return integer;
}
public volatile Object info(Object obj)
{
return info((Integer)obj);
}
}
info(Object)方法通过调用子类的info(Integer)方法搭起了父类和子类的桥梁,也就是说,info(Object obj)这个方法起到了连接父类和子类的作用,使得Java的多态在泛型情况下依然有效。
当然,我们在使用基于泛型的多态时不必过多的考虑“桥接方法”,Java编译器会帮我们打理好一切。
关于桥接方法的更多信息可以参考:JLS的相关章节。
参考资料
•http://docs.oracle.com/javase/tutorial/java/generics/index.html
•http://docs.oracle.com/javase/tutorial/extra/generics/index.html
来源:http://softlab.sdut.edu.cn/blog/subaochen/2017/01/generics-type-erasure/
猜你喜欢
- 本文实例讲述了Android开发实现拨打电话与发送信息的方法。分享给大家供大家参考,具体如下:xml布局:<LinearLayout
- 方法引用和构造器引用了解了 Lambda 表达式有一段时间了,但是都没有怎么练习,一直停留在最低层次的了解程度,这对于追求技术进步的人来说确
- Android 6本文根据我个人的开发经验,总结了从 Android 6 - Android 13 重要的行为变更。当然,这不是 Andro
- * 与过滤器在讲Spring boot之前,我们先了解一下过滤器和 * 。这两者在功能方面很类似,但是在具体技术实现方面,差距还是比较大的
- 之前学习了设计模式原型模式,在原型模式中就提到了对象的深拷贝。深拷贝指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一
- 客户端代码using System;using System.Collections.Generic;using System.Compon
- 前言JVM是Java中比较难理解和掌握的一部分,也是面试中被问的比较多的,掌握好JVM底层原理有助于我们在开发中写出效率更高的代码,可以让我
- 前言表单提交是最常见的数据提交方式,我们经常会填写表单信息,比如用户名,身份证,手机号等等,因此就会产生身份证是否合法,用户名是否为空,虽然
- 本文实例讲述了C#实现对二维数组排序的方法。分享给大家供大家参考。具体实现方法如下:/// <summary>/// A gen
- 本文将介绍Java在ICPC快速IO实现方法,下面看看
- 前言Handler,可谓是面试题中的一个霸主了。在我《面试回忆录》中,几乎没有哪家公司,在面试的时候是不问这个问题的。简单一点,问问使用流程
- 过早提升(Premature Promotion)提升速率(promotion rate), 用于衡量单位时间内从年轻代提升到老年代的数据量
- 管理fragment的生命周期有些像管理activity的生命周期。Fragment可以生存在三种状态:Resumed:Fragment在一
- 界面中控件较多的话,每个控件都设置setOnClickListener(this)是很麻烦的,为此抽出了一个Context的扩展类:fun
- Redis缓存中间件缓存是什么  所谓缓存就是数据交换的缓冲区(称作Cache [ k&aeli
- 在spring boot中,简单几步,使用spring AOP实现一个 * :1、引入依赖:<dependency> &nbs
- 这篇文章主要介绍了Spring ApplicationListener * 用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具
- 本文实例为大家分享了Winform实现导入导出Excel文件的具体代码,供大家参考,具体内容如下/// <summary> &n
- 参考:How to catch an Exception from a threadIs there a way to make Runna
- 在 APK 开发中,通过 Java 代码来打开系统的安装程序以安装 APK 并不是什么难事,一般的 Android 系统都有开放这一功能。但