一、泛型擦除
在编译期间,所有泛型信息都会被擦除掉,在生成的字节码中是不包括泛型中的类型信息的
直接举个例子
public class Main2<T> {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
List<Integer> list1 = new ArrayList<>();
list1.add(111);
System.out.println(list.getClass() == list1.getClass());
}
//输出为 true
}
看一下反编译后的结果
看到第 11 行和第 31 行,在 调用add 方法的时候,已经将泛型信息去掉了,显示此方法的参数类型是Object。
为什么是 Object 呢?
因为“泛型擦除”机制, List 的参数类是
public interface List<E> extends Collection<E>
此时因为 E 是一个无上限的变量,因此就直接用最大的一个范围来代表,即 Object 类型
举个例子:
public class Main2<T extends Number> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Main2<Integer> main2 = new Main2<>();
main2.setT(12);
Main2<Double> main21 = new Main2<>();
main21.setT(12.32);
}
}
我们对类的参数类型做一个约束,再来看反编译后的结果
可以清晰的看到,在第 14 行和第 32 行,泛型擦除后的类型不再是 Object,而是我们规定的上限 Number 类
二、擦除前的检查
还是举刚才的例子,既然说是泛型擦除,那么为什么下面那个语句会报错呢?不是说已经将类型变为 Object 了吗?
public class Main2<T> {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
list.add(123); //编译错误
}
}
答案是:java编译器会在编译之前检查泛型的类型,只有通过之后才可以进行编译并擦除泛型
三、擦除后进行类型转换
1.对泛型表达式进行转换
既然擦除了泛型类型,那么最终我们得到 String 类,Integer 类又是怎么获取的?还是刚才那个例子,最后我们用 get 方法进行获取
public class Main2<T> {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("aaa");
// list.get(0); //语句1
String str = list.get(0);
}
}
对于语句1,调用 get 方法获取的参数的类型就是擦除类型后的 Object 类型,并没有进行类型转换,可以通过反编译来验证
而对于语句 1 的下面一条语句,结果就不同了,其实他经历了两步
- 调用 get 方法
- 将返回的 Object 类强制转换为 String 类
通过反编译来验证一下吧
注意第 24 行的 checkcast,直接将 Object 类转换成了 String 类
所以不是在get方法里强转的,而是在你调用的地方强转的。泛型作为返回值,参数类型,变量类型等等都会被擦除改成Object类型,与直接使用Object类型的区别在于:
使用泛型,在被调用且有默认接收者时,会根据传递的泛型类型自动且隐式的进行类型转换,而不需要人为的设置转换类型。这也是泛型的好处,自动类型转换。
2.对泛型方法进行转换
假设有这么一个类父类
public class Pair<T> {
private T first;
public Pair() {}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
我们使用一个子类来继承这个父类,然后使用多态调用父类的方法
public class Test2 extends Pair<String> {
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.setFirst(new String());
test2.getFirst();
}
}
然后我们执行这段代码,然后通过反编译来看下运行的过程
通过第 11 行和第 15行可以看出,当子类调用父类的方法且在编译的时候,传入的父类的泛型方法的类型已经被擦除了,相当于是这样的,因为没有对类型设置上限,因此就是 Object
public class Pair {
private Object first;
public Pair() {}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
}
此时调用父类的方法,得到的类型实则就是 Object 类。当然,你也可以像上面一样,对得到的数据进行类型转换
知道这一点之后,我们在来看一个例子,假如子类重写父类的方法会怎么样
public class Test2 extends Pair<String> {
@Override
public void setFirst(String first) {
super.setFirst(first);
}
@Override
public String getFirst() {
return super.getFirst();
}
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.setFirst("aaa");
test2.getFirst();
}
}
此时子类在调用的时候,其实由小到大进行动态搜索,包括
// 继承父类的已经类型擦除后的方法
public void setFirst(Object first){...}
// 子类自己新增的方法
public void setFirst(String first){...}
// 其他继承 Object 的方法
乍一看,这并不是多态啊,如果真是多态,重写的应该是 void setFirst(Object first) 方法,但是这里却多出来了 void setFirst(String first) 方法。可见类型擦除和多态之间发生了冲突!!!
那么java编译器又是怎么处理这个冲突的呢?使用桥方法。之间看反编译的结果
可以看到,编译器在 Test2 类中生成了两个桥方法
public void setFirst(Object first) {
setFirst((String)first);
}
public Object getFirst() {
//这里返回的 String 类型的 getFirst 方法
return getFirst();
}
这两个<u>桥方法</u>,相当于重写了父类的两个方法,最终还是会调用子类的方法,相当于实现了子类对父类的重写
桥方法为子类和父类之间架起了一座连通的桥梁,真正实现了泛型继承中的动态绑定,也很好的解决了类型擦除与多态之间的冲突
3.泛型在静态方法和静态类中的问题
<u>泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数</u>
举例说明:
public class Test2<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
但是要注意区分下面的一种情况:
public class Test2<T> {
public static <T >T show(T one){//这是正确的
return null;
}
}
因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T。
四、其他问题
假如我们将一个定义了泛型参数的对象赋值给一个没有定义泛型参数的对象时
public class Test3 {
public static void main(String[] args) {
Pair<String> stringPair = new Pair<>();
stringPair.setFirst("asv");
// 1
String first = stringPair.getFirst();
// 2
Pair pair = stringPair;
// 此时的 pair 是 Object 类的,因此需要手动的进行类型转换
String s = (String)pair.getFirst();
}
}
结果是一样的,不同的是,第1步会先调用 getFirst 方法,然后会泛型擦除,之后就是进行类型转换;对于第2步,把 Pair 赋值给 Pair,这个时候原来的泛型类型(String)就会被扔掉,变为 Pair
另一个例子
public class Test3 {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("aaa");
List list = stringList;
list.add(123);
list.add(123.123);
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
String s = stringList.get(0);
String s1 = stringList.get(1); //运行时错误
}
}
结果是:aaa 123 123.123
可以看到,当把 List 赋给 List 之后,小的范围变成了大的范围,此时 List 也变成了 Object 类型的了,但这只是代表能够容纳的参数的类型变多了,如下如所示
因为 stringList 对应的泛型类型还是 String 类型的,因此在泛型擦除后进行的类型转换,还是转为 String 类
因为里面有 Integer 类和 Double 类型,此时会报错
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
参考:
https://blog.csdn.net/lonelyroamer/article/details/7868820
网友评论