一次泛型擦除的坑

博客探讨了在使用MyBatis时遇到的类型转换异常,特别是涉及数据库查询结果到List的转换。作者指出,MyBatis在进行类型擦除后,可能会导致JVM在强转时抛出ClassCastException。解决方案是使用String.valueOf避免直接的类型转换。文章还提及了mapper文件的管理策略,并邀请读者分享更好的做法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先说报错:
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
再说思路。看到这个类转换异常,感觉很好解决。结果看到报错的地方有点晕了。

默认

在这里插入图片描述
第一种其实默认调用的是toString方法,自己最开始也是加的一个toString,因为我想着不管是什么类型,toString方法总是不会报错的吧。结果就报错了。改为第二种办法。String.ValueOf就没有问题了。
来进行分析。

我这个list是从数据库查出来的。是通过mybatis框架得到的。后来发现mybatis进行代理的时候,会进行类型擦除,会把接口定义的List 这种统统擦除,设置为数据库返回的真实类型。
然后jvm编译的时候,也是类型擦除的,但是有些List、Map的get方法,其实是保留了这个对应的类型,也就是调用get方法时,会转换成对应的泛型。这就出现了一个强转。强转就是发生在这里。

那为什么String.valueOf不会报错呢。是因为这个时候还没有出发get之后的toString,这个get得到的其实是数据库返回的Double类型。强转就会报错。
String.valueOf会自动适配对应的类型。接受的参数比较多。

结束。附上几张贴图。

但是一旦你需要操作List的中的元素,在取出元素时,JVM就发现你要的类型是DictContent 而实际上是LinkedHashMap,就会抛出类型转换异常
在这里插入图片描述

话说我现在mapper文件都是自动生成的。然后有些修改,我会搞一个手写的mapper,这样每次修改就不会覆盖了。有其他更好的办法吗?欢迎留言讨论。

<think>好的,我现在要回答用户关于Java擦除的问题。首先,我需要整理一下已有的引用资料,确保理解正确。用户提供了五个引用,内容涉及擦除的原理、实现方式、擦除规则、影响以及相关的限制和解决方案。 首先,擦除的原理是什么?根据引用2和引用3,Java在编译时会被擦除,替换为原生类,并在需要的地方插入强制转换。这意味着在运行时,信息不存在,比如ArrayList<String>和ArrayList<Integer>在运行时都是同一个类。 接下来,擦除的规则。引用1和引用4提到有无界擦除、上界擦除和下界擦除。例如,无界的参数会被替换为Object,有上界的替换为上界类,有下界的可能替换为Object。需要确认这些规则的具体情况,以及如何验证这些规则,比如通过反射或者查看字节码。 然后是影响部分。用户可能关心擦除带来的问题,比如类信息在运行时不可用,导致无法直接实例化,或者无法进行类检查。引用4中提到了两个经典问题:在类中获取实际类和在接口回调中获取类,可能需要通过反射技巧来解决,比如获取父类的参数化类。 另外,引用5提到原始类不支持,比如不能使用List<int>,而必须用包装类,这会带来自动装箱的开销。同时,项目Valhalla可能在未来解决这个问题,但现在还是限制。 现在需要将这些信息组织成结构清晰的回答,分点解释原理、影响,并举例说明。同时,确保使用正确的中文表达和LaTeX格式,比如代码块用反引号,数学公式用$...$,独立公式用$$...$$。不过根据用户提供的示例,似乎数学公式的例子并不直接相关,可能在这个问题中不需要使用数学公式,但需要遵循格式要求。不过用户的问题是关于Java,可能涉及代码示例,所以需要正确使用代码块的格式。 最后生成相关问题,可能包括擦除的优缺点,如何绕过擦除限制,桥接方法的作用,类擦除与类安全的冲突,以及原始类不支持的原因等。 需要检查引用是否正确标注,比如引用[^1]到[^5]是否正确对应提供的资料。根据用户提供的引用,引用1到5分别对应不同的内容,需要确保在回答中正确引用,比如提到桥接方法时引用,提到擦除原理时引用[^2][^3],原始类限制引用[^5]等。 现在组织回答的结构: 1. 擦除的原理:编译时擦除,替换为原生类,插入强制转换。 2. 擦除规则:无界、上界、下界擦除的具体情况。 3. 主要影响: - 运行时类信息丢失 - 类检查限制 - 桥接方法的作用 - 原始类不支持 4. 解决方案和技巧:反射获取信息,桥接方法等。 5. 示例代码:比如类擦除后的代码示例。 6. 相关问题。 在回答过程中,需要确保每个引用的位置正确,比如提到桥接方法时引用,提到擦除实现引用[^2],原始类问题引用。 另外,用户可能对类擦除带来的类安全问题有疑问,比如为什么Java选择这种方式,可能与兼容性有关,保持与旧版本字节码的兼容性,引用2提到这是为了作为语法糖实现,确保兼容性。 可能还需要提到类擦除导致的一些常见错误,比如无法创建数组,或者instanceof无法使用,这些可以结合引用中的例子说明。 检查是否有遗漏的重要点,比如桥接方法在保持多态性的作用,引用1中的演示案例可能需要解释,但在回答中可能需要简化。 最后,确保回答符合用户的要求:结构清晰,分步骤解释,真实可靠,引用正确,使用中文,代码块正确,生成相关问题。</think>### Java擦除原理及影响解析 #### 一、擦除原理 Java通过**类擦除(Type Erasure)**实现,其核心规则为: 1. **编译期擦除**:信息在编译后被替换为原始类(如`Object`或指定上界类),并插入强制类转换代码。 - 无界类(如`<T>`)擦除为`Object`,例如`List<T>`变为`List`。 - 有上界的类(如`<T extends Number>`)擦除为上界类`Number`。 - 有下界的类(如`<? super Integer>`)擦除为`Object`。 2. **运行时无信息**:类在运行时会丢失类参数信息,例如`List<String>`与`List<Integer>`在JVM中均为`List`。 ```java // 编译前 List<String> list = new ArrayList<>(); // 编译后(通过反编译查看) List list = new ArrayList(); // 类参数被擦除 String s = (String) list.get(0); // 插入强制转换 ``` #### 二、主要影响 1. **运行时类限制** - **无法通过反射获取**:例如`new ArrayList<String>().getClass()`无法得知`String`类参数[^4]。 - **无法直接实例化对象**:`new T()`会编译报错,因为实际类未知[^3]。 2. **类安全检查局限性** - 转换错误可能在运行时才暴露。例如将`List<Integer>`强制转换为`List<String>`时,编译期无法检测。 3. **桥接方法(Bridge Method)** - 为实现多态性,编译器会生成桥接方法。例如类继承时,子类需通过桥接方法覆盖父类方法[^1]。 4. **原始类不支持** - 如`List<int>`不合法,需使用包装类`Integer`,导致自动装箱/拆箱的性能开销。 #### 三、解决方案示例 1. **获取实际类** 通过反射获取父类或接口的参数: ```java public class MyList<T> { public Class<T> getType() { return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; } } ``` 2. **绕过实例化限制** 使用工厂方法传递`Class<T>`参数: ```java public static <T> T createInstance(Class<T> clazz) throws Exception { return clazz.getDeclaredConstructor().newInstance(); } ``` ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值