泛型是什么,泛型擦除呢?
详细讲解
享学课堂移动互联网系统课程:架构师筑基必备技能《架构设计中必不可少的泛型-Java泛型的定义与原理》
这道题想考察什么?
泛型
考察的知识点
泛型的特点和优缺点以及泛型擦除
考生应该如何回答
泛型就是一种就是一种不确定的数据类型。在Java中有着重要的地位,在面向对象编程及各种设计模式中都有非常广泛的应用。
泛型的优点
我们为什么需要使用泛型:
- 适用于多种数据类型执行相同的代码,例如两个数据相加:
public int addInt(int x,int y){
return x+y;
}
public float addFloat(float x,float y){
return x+y;
}
不同的类型,我们就需要增加不同的方法,但是使用泛型那我们的代表将变为:
public <T> T addInt(T x,T y){
return x+y;
}
- 编译检查,例如下面代码
List<String> list = new ArrayList();
list.add(10);//①
list.add("享学");
String name = list.get(2);//②
因为我们指定了List泛型类型为String,因此在代码1处编译时会报错。而在代码2处,不再需要做类型强转。
泛型的缺点
- 静态域或者方法里不能引用泛型变量,因为泛型是在new对象的时候才知道,而类的构造方法是在静态变量之后执行。
- 不能捕获泛型类对象
泛型擦除
Jdk中实现的泛型实际上是伪泛型,例如泛型类 Fruit<T> ,编译时 T 会被擦除,成为 Object。但是泛型擦除会带来一个复杂的问题:
桥方法
有如下代码:
public class Parent<T> {
public void setSrc(T src){
}
}
public class Child extends Parent<String>{
@Override
public void setSrc(String src) {
super.setSrc(src);
}
}
Parent类是一个泛型类,在经过编译时泛型擦除后其中setSrc(T)
将会变为setSrc(Object)
;而Child类继承与Parent并且指定了泛型类型为String。那么经过编译后这两个类应该变为:
public class Parent {
public void setSrc(Object src){
}
}
public class Child extends Parent{
@Override
public void setSrc(String src) {
super.setSrc(src);
}
}
父类存在setSrc(Object)
,而子类则是setSrc(String)
。这明显是两个不同的方法,按照Java的重写规则,子类并没有重写父类的方法,而是重载。
所以实际上子类中存在两个setSrc
方法。一个自己的,一个是继承自父类的:
public void setSrc(String src)
public void setSrc(Object src)
那么当我们:
Parent o = new Child();
o.setSrc("1");
此时o实际类型是Child,静态类型是Parent。按照Java规则,会调用父类中的setSrc(Object)
,如:
public class A{
public void setValue(Object value){
System.out.println("Object");
}
}
public class B extends A{
public void setValue(String value){
System.out.println("String");
}
}
public static void main(String[] args) {
A a = new B();
a.setValue("1");
a.setValue(11);
}
上诉代码会输出两次”Object“。然而在泛型中却不符合此规则,因为 Java 编译器帮我们处理了这种情况,在泛型中引入了"Bridge Method"—桥方法。通过查看Child.class的字节码文件 :
public void setSrc(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #2 // Method Parent.setSrc:(Ljava/lang/Object;)V
5: return
LineNumberTable:
line 4: 0
line 6: 5
public void setSrc(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #3 // class java/lang/String
5: invokevirtual #4 // Method setSrc:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
}
可以看到 Child 类中有两个 setSrc
方法,一个参数为 String 类型,一个参数为 Object 类型,参数为 Object 类型。而参数为Object的setSrc
方法可以在flags中看到ACC_BRIDGE和ACC_SYNTHETIC 。其中ACC_BRIDGE用于说明这个方法是由编译生成的桥接方法,ACC_SYNTHETIC说明这个方法是由编译器生成,并且不会在源代码中出现。
在setSrc(Object)
桥方法可以看到实际上会使用checkcast
先进行类型转换检查,然后执行invokevirtual
调用setSrc(String)
方法,这样就避免了我们还能调用到父类的方法。