3.泛型擦除引起的问题及解决方法

简介: Java泛型在编译时进行类型检查并擦除类型信息,类型检查针对引用而非对象本身。擦除后泛型转为原始类型,通过自动强转实现安全取值。泛型不支持基本类型,静态成员不能使用类的泛型参数,但泛型方法可独立定义类型参数。instanceof不可用于泛型类型判断。

3.1 先检查,再编译以及编译的对应和引用传递问题
这里我们可能会有一个疑问,既然说类型变量会在编译的时候擦除掉,那为什么上面的ArrayList中添加String类型的时候就报错了呢,因为String编译时候也会变成Object啊?
A:因为JAVA编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译的。那么这个检查到底是针对谁的,我们需要再明确下
A2:如我们上面代码是:
现在我们写成:
此时如果我们与之前的代码兼容,各种引用传值之间,必然会出现下面情况:
这样没错,但是会有个编译时警告,不过在第一种情况下,可以实现与完全使用泛型参数一样的效果,但是第二种没有效果。
因为类型检查是编译时完成的,new ArrayList()只是在内存中开辟一个存储空间,可以存储任何类型的对象,而真正涉及类型检查的是“它的引用”,即list1的方法调用,如add方法,所以list1引用能够完成泛型类型检查(前面声明了String),但是list2(后面声明的只是开辟内存空间,不涉及)由于前面的声明没有添加泛型,所以不行。
所以这里我们也大概知道了,所谓的类型(泛型)检查,是针对引用的。谁是一个引用,用这个引用调用泛型方法,就会对这个引用所调用的方法进行类型检查,而无关它真正引用的对象。
3.2 自动类型转换
因为类型擦除的问题,所以所有的泛型类型变量在最后都会被替换成原始类型,既然都被替换了,那么为什么获取的时候,不需要进行强制类型转换呢?可以看下 ArrayList.get() 方法
可以看到,在return之前,会根据泛型变量进行强转。假设泛型类型变量为Date,虽然泛型信息会被擦除掉,但是会将(E) elementData[index],编译为(Date) elementData[index]。所以我们不用自己进行强转。当存取一个泛型域时也会自动插入强制类型转换。假设Pair类的value域是public的,那么表达式:
也会自动地在结果字节码中插入强制类型转换。
3.3 泛型擦除与多态的冲突与解决方法
假设有一个泛型类
然后有一个子类需要继承
在这个子类中,我们设定父类的泛型类型为Pair,在子类中,我们覆盖了父类的两个方法,我们的原意是这样的:将父类的泛型类型限定为Date,那么父类里面的两个方法的参数都为Date类型。
所以,我们在子类中重写这两个方法一点问题也没有,实际上,从他们的@Override标签中也可以看到,一点问题也没有,实际上是这样的吗?
分析:实际上,类型擦除后,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子:
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
class Pair {
private Object value;

public Object getValue() {  
    return value;  
}  

public void setValue(Object  value) {  
    this.value = value;  
}  

}
而此时,子类中类型依然是Date,这如果还是在继承关系中,那么根本就不是重写,而是重载了。通过反编译会发现子类中的方法Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。
3.4 泛型类型变量不能是基本数据类型
不能用类型参数替换基本类型。就比如,没有ArrayList,只有ArrayList。因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储double值,只能引用Double的值。
3.5 编译时集合的instanceof(可能面试考察)
Java
运行代码
复制代码
1
ArrayList arrayList = new ArrayList();
因为类型擦除之后,ArrayList只剩下原始类型,泛型信息String不存在了。那么,编译时进行类型查询的时候使用下面的方法是错误的
Java
运行代码
复制代码
1
if( arrayList instanceof ArrayList)
3.6 泛型在静态方法和静态类中的问题(可能面试考察)
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数,举例说明:
Java
运行代码
复制代码
1
2
3
4
5
6
public class Test2 {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在对象定义时候指定的,而静态变量和静态方法是不需要通过对象来调用的,对象都没有创建,如何确定这个泛型是何类型呢?所以说上面的代码明显是错误的。
但是需要注意下面的一种特殊情况
Java
运行代码
复制代码
1
2
3
4
5
public class Test2 {
public static T show(T one){ //这是正确的
return null;
}
}
因为这是一个泛型方法,在泛型方法中使用过的T是自己在方法中定义的T,而不是泛型中的T

相关文章
|
17小时前
3.5.3 排序查询
sort()方法用于对查询结果排序,1为升序,-1为降序,可按多字段排序。语法:db.集合名.find().sort({字段:1/-1})。与skip()、limit()联用时,执行顺序为先排序,再跳过,最后限制显示。
|
18小时前
请求体参数
护理项目实体类,包含项目编号、名称、单位、价格、图片、护理要求等信息,支持状态(启用/禁用)与排序管理,用于养老服务中护理项目的配置与展示。
|
17小时前
|
Java 调度
ScheduleExecutorService
当ScheduledExecutorService的调度周期小于任务执行时间时,任务不会并发执行,而是等待前一次完成后再立即执行。因任务被放入延时队列,下次触发时间基于上一次设定周期计算,若未完成则延迟执行,实际调度周期等于任务执行时间,避免了任务堆积和并发冲突。
|
17小时前
3.5.2 分页列表查询
使用limit()限制查询返回的记录数,skip()跳过指定数量数据,实现分页。如每页2条:第一页skip(0).limit(2),第二页skip(2).limit(2),以此类推,默认limit为20,skip为0。
|
17小时前
3.5 文档的分页查询
统计查询使用count()方法,可统计集合中符合条件的记录数量。语法为db.collection.count(query, options)。如统计comment集合全部记录:db.comment.count();按条件统计userid为1003的记录数:db.comment.count({userid:"1003"})。默认返回所有匹配文档的数量。
|
18小时前
3.3.3 集合的删除
集合删除语法为 `db.collection.drop()` 或 `db.集合.drop()`,成功返回 true,失败返回 false。例如:`db.mycollection.drop()` 可删除 mycollection 集合。
|
17小时前
|
存储 NoSQL MongoDB
3.2 数据库操作
MongoDB中使用`use 数据库名`选择或创建数据库,若不存在则自动创建。通过`show dbs`查看所有数据库,`db`查看当前库。集合需插入文档后才真正创建。数据库名须为非空、小写、不超过64字节的UTF-8字符串,不可含特殊字符。保留库包括admin(权限管理)、local(本地数据)和config(分片信息)。
|
17小时前
|
安全
2.什么是泛型擦除后保留的原始类型
泛型擦除后生成原始类型,类型参数被替换为限定类型或Object。如`Pair<T>`变为`Pair`,字段和方法中的T均替换为Object;若`T extends Comparable`,则替换为Comparable,确保类型安全与兼容性。
|
17小时前
|
存储 NoSQL Linux
2.4 Linux系统中的安装启动和连接
本文介绍在Linux系统部署单机MongoDB用于生产环境的完整步骤,包括下载、解压、目录配置、日志与数据路径设置、配置文件编写及服务启停方法。操作类似Windows,通过配置`mongod.conf`实现后台运行,支持命令行与图形工具连接,并提供防火墙处理与安全关闭服务方案,确保稳定运行。