1、前言
有下面一段代码,执行以下,看会有什么问题:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import java.math.BigDecimal;
public class MyLanting {
public static void main(String[] args) {
// 创建一个MyOption,泛型类型为 BigDecimal ,但是多态转换成了 接口(相当于抹除了类型信息)
IOption option = new MyOption();
// 传入一个字符串
option.test("fff");
}
}
// 泛型接口
interface IOption<T> {
void test(T t);
}
// 泛型接口的一种实现
class MyOption implements IOption<BigDecimal> {
public void test(BigDecimal bigDecimal) {
System.out.println(bigDecimal);
}
}
当然,不出意外就会报下面的错误信息:
1 | Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.math.BigDecimal |
为什么会出现上面的问题呢?我们一步步来看
2、泛型类型接口编译成class做了什么?
我们使用 javap -v IOption.class
去查看字节码信息,可以看到下面信息:
1 | Classfile /E:/workspace/DbunitUtil/target/test-classes/com/lcj/test/IOption.class |
通过字节码信息,我们可以看到,test()方法在编译成class后,形参变成了 Object ,另外 Signature记录了泛型信息;因此,我们创建的 void test(T t);
方法直接“转”成了public abstract void test(Object t)
。
3、实现类编译成class做了什么?
我们再看看实现类MyOption
的字节码:
1 | Classfile /E:/workspace/DbunitUtil/target/test-classes/com/lcj/test/MyOption.class |
通过字节码,我们可以看到,MyOption
类在class字节码中实际有两个方法,分别是:
public void test(java.math.BigDecimal);
public void test(java.lang.Object);
其中 public void test(java.lang.Object);
方法的实现代码:
1 | 0: aload_0 |
可以看出,实际是将输入参数 进行了checkcast
的强行类型转换,再调用BigDecimal形参的方法,换成java实现代码大致就是:
1 | public void test(Object bigDecimal) { |
这个方法,在java里面,我们称之为:桥方法
4、问题分析
回到我们第一节中的问题,我们来看看下面的关键代码:
1 | IOption option = new MyOption(); |
- 上面的代码首先创建了一个
MyOption
类, - 紧接着将实例赋值给了一个
IOption
类型,我们知道,IOption
的test
方法实际是Object
的,因此,option.test("fff");
是符合语法规则的, - 由于多态特性,实际
option.test("fff");
方法会调用到MyOption
的桥方法public void test(java.lang.Object)`,桥方法的实现很单一,就是将参数强行类型转换,再调实际的泛型方法,
- 由于传入的
"fff"
是字符串类型,在将字符串类型强行类型转换成BigDecimal
类型时,就报错了。
5、总结
- 泛型在底层实现时,其实是将泛型类型进行了类型擦除
- 实现类在实现泛型时,实际会出现两个方法,一个是原始的 Object 类型的,一个是实际的实现类型
- 在调用泛型时,千万不要随便转换成抹除了类型的实例,否则可能会误调用到桥方法,导致类型转换失败。