编译 .java 生成的 .class (字节码文件)中数据类型必须是确定好的。 如果一个 class 是泛型类,或者含有泛型方法,那么编译器在编译时会将其中的类型变量去掉,生成一个与泛型类同名的原始类。在 原始类class文件 中的是其真正的类型(原始类型)。
注:
<>所修饰的部分(例:< T >)直接被擦除,而之后的用到的类型变量( T )会被原始类型代替。
原始类型:类型限界(无类型限界为Object)
定义泛型类Generic1和Generic2
class Generic1<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
class Generic2<T extends A> { // A为类型限界
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 自定义的类
class A {
String aName = "A";
}
class B extends A {
String bName = "B";
}
Generic1和Generic2类分别经过类型擦除后生产的(原始类)字节码如下
/*
* 原始类型为:Object
*/
class Generic1{
Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
/*
* 原始类型为:A
*/
class Generic2 {
A value;
public A getValue() {
return value;
}
public void setValue(A value) {
this.value = value;
}
}
总结:
Generic1中的
public T getValue() {
return value;
}
调用getValue方法时
Generic1<String> g1 = new Generic1<>();
g1.setValue("Hello world!");
String s1 = g1.getValue();// 这里编译器编译时处理成:String s1 = (String) g1.getValue()
由于原始类型是Object,返回的value的类型是Object,但是在调用这个方法的地方会根据类型变量进行强转(做了一个checkcast()操作,即检查< String >中的类型并强转) ArrayList中的
E elementData(int index) {
return (E) elementData[index];
}
public E get(int index) {
rangeCheck(index);
return elementData(index);// 调用elementData方法
}
调用get方法时
ArrayList<Date> list = new ArrayList<Date>();
list.add(new Date());
Date myDate = list.get(0);
由于原始类型是Object,方法返回值是Object,但是在调用时会进行强制类型转换。 以上需要注意的是:不是在方法里强转,是在调用的地方强转。
定义一个父类
class Generic<T> {
T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
定义一个子类,重写父类方法
class MyGeneric extends Generic<String> {
@Override
public void setValue(String value) {
super.setValue(value);
}
@Override
public String getValue() {
return super.getValue();
}
}
子类重写父类的方法,但是
// 继承父类的已经类型擦除后的方法
public void setValue(Object value){...}
public T getValue(){...}
// 子类新增(重写)的方法
public void setValue(String value){...}
public String getValue(){...}
由于参数类型不同这并不能算是重写,为了达到重写的目的,编译器使用桥方法解决。 编译器在 MyGeneric 类中生成了两个桥方法(这两个桥方法会调用子类新增的方法)
public void setValue(Object value) {
setValue((String) value);// 子类新增的setValue(String value)方法
}
public Object getValue() {
return getValue();// 子类新增的getValue()方法(返回的 String 类型的 getValue 方法)
}
也就是说在重写方法后的子类中通过编译器会变成如下情形
class MyGeneric extends Generic<String> {
public void setValue(String value){...} // 自己定义(重写)的方法
public void setValue(Object value){...} // 编译器生成的桥方法
public String getValue(){...} // 自己定义(重写)的方法
public Object getValue(){...} // 编译器生成的桥方法
}
值得注意的是getValue方法: 编译器允许在同一个类中出现方法签名相同的多个方法吗? 方法签名(方法名+参数列表)用来确定一个方法; 人为是不能在同一个类中编写出方法签名一样的多个方法,否则编译器会报错; 但是,编译器自己能够创建出方法签名一样而返回类型不同的方法,JVM会用参数类型和返回类型来确定一个方法。