java中的泛型是采用类型擦除的方式来实现,也即编译后所有原始类型的泛型类都共享同一份目标代码,例如这里的A
编译器编译为A,那么编译器对于引用类中泛型的方法,也即泛型方法进行类型擦除操作时是如何实现的呢?答案是采用最左边类型(当前T的初始具体父类型)来代替。如下面代码经过编译器后生成:
编译前的源代码
class A<T> {
T get(final T t) {
return t;
}
}
编译后的class
A extends java.lang.Object{
A();
java.lang.Object get(java.lang.Object);
}
从上面的代码中可以看出,T被Object代替,也即T的当前父类为Object。如果A
,那么编译后就会用Comparable代替Object。那进行下面代码分析,B继承了泛型类A
时重写泛型类中的泛型方法,此时重写是用Object来作为返回值和参数,编译器产生了错误,看下面代码。
class A<T> {
T get(final T t) {
return t;
}
}
/**
* 通过一个类继承了一个泛型类,重写方法时的问题.
* @since JDK 1.5.0
*/
class B extends A<String> {
/**
*@ERROR: Compile-error
* 条件:
* 1、这两个方法(B.get和A.get)具有相同的名称,也即get
* 2、A.get(String)方法B是可以访问得到
* 3、B.get(Object)和A.get(String)的签名signature不是父子关系
* 4、这两个方法具有相同的擦出记号?没明白什么意思....
* @param obj
* @return
*/
Object get(final Object obj) {
return obj;
}
}
错误消息为名称冲突:类型 B 的方法 get(Object)与类型 A 的 get(T)具有相同的擦除记号,但是未将它覆盖。乍一看完全不明白这什么意思,因为从A.class来看,擦除泛型后代码中包含的是Object get(Object),此时在B中定义个相同方法,并应该是符合java中的重写语义的吗?从jls中翻阅可以得吃override条件是满足的,那么编译器为什么不能通过编译?
暂时将问题放一边,将代码进行稍加修改后,编译器编译成功,测试通过,为什么它可以呢?javap B 或者 javap -verbose B输出指令集进行查看编译结果。
class B extends A<String> {
/**
*@ERROR: Compile-error
* 条件:
* 1、这两个方法(B.get和A.get)具有相同的名称,也即get
* 2、A.get(String)方法B是可以访问得到
* 3、B.get(Object)和A.get(String)的签名signature不是父子关系
* 4、这两个方法具有相同的擦出记号?没明白什么意思....
* @param obj
* @return
*/
String get(final String str) {
return str;
}
}
B extends A{
B();
java.lang.String get(java.lang.String);
java.lang.Object get(java.lang.Object);
}
//具体get方法
java.lang.Object get(java.lang.Object);
Code:
Stack=2, Locals=2, Args_size=2
0: aload_0
1: aload_1
2: checkcast #19; //class java/lang/String
5: invokevirtual #21; //Method get:(Ljava/lang/String;)Ljava/lang/String;
8: areturn
LineNumberTable:
line 1: 0
}
在B中只有String get(String),那么Object get(Object)从何而来。这是编译器自动生成的一段代码,那么它的目的是什么?从它的内部指定可以看出,调用String get(String)来实现功能,那么它为什么要调用String get(String)呢?这又得从java泛型擦除说起,B继承了A
,那么从编程角度来讲,A
中的泛型方法get应该就是String get(String),此时B中的String get(String)就是重写A中的String get(String),A
是如何来实现的呢?就是通过产生一个桥接方法Object get(Object)来实现,从这里看出java泛型中利用的桥接模式。
写到这里,我想大家应该清楚在B中写入一个Object get(Object)方法是为什么会有complie-error了,就是因为编译器产生一个Object get(Object),此时源代码中又有一个Object get(Object),一个class中同一个方法,具有相同签名和名称的方法重复定义是不符合java语法规定,所以有complie-error。
class A<T> {
T get(final T t) {
return t;
}
}
interface C {
E get(final E e);
}
class D extends A<String> implements C<Integer> {
/**
* 名称冲突:类型 C 的方法 get(E)与类型 A 的 get(T)具有相同的擦除记号,但是未将它覆盖
* {@inheritDoc}
* @see s8_4.C#get(java.lang.Object) 这里是C接口中的实现
*/
public Integer get(final Integer t) {
return null;
}
/**
* 名称冲突:类型 C 的方法 get(E)与类型 A 的 get(T)具有相同的擦除记号,但是未将它覆盖
* {@inheritDoc}
* @see s8_4.A#get(java.lang.Object) 这里是A类的get重写
*/
public String get(final String str) {
return null;
}
}
这个错误原因也是一样。