举例来说明类型擦除在继承中引入的问题以及编译器是如何解决的。
有一个Pair类:
package generic;
/**
* @version 1.00 2004-05-10
* @author Cay Horstmann
*/
public class Pair<T>
{
private T first;
private T second;
public Pair() { first = null; second = null; }
public Pair(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
Pair类类型擦除后变成这样:
package generic;
/**
* pair<T>类型擦出后的版本,即pair<T>得原始类型;
* 擦除规则是,删除类名后面的类型变量,并将类中用到
* 类型变量的地方替换为类型变量的限定类型(
* 如果没有限定类型就用Object)
* @author yuncong
*
*/
public class Pair {
private Object first;
private Object second;
public Pair() { first = null; second = null; }
public Pair(Object first, Object second) { this.first = first; this.second = second; }
public Object getFirst() { return first; }
public Object getSecond() { return second; }
public void setFirst(Object newValue) { first = newValue; }
public void setSecond(Object newValue) { second = newValue; }
}
DateInterval继承自Pair<Date>,它表示一个时间区间,源码如下:
package generic;
import java.util.Date;
public class DateInterval extends Pair<Date>{
public void setSecond(Date second) {
if (second.compareTo(getFirst()) >= 0) {
super.setSecond(second);
}
}
}
DateInterval类型擦除后的样子如下:
package generic;
import java.util.Date;
public class DateInterval extends Pair{
public void setSecond(Date second) {
if (second.compareTo(getFirst()) >= 0) {
super.setSecond(second);
}
}
}
下面是一个测试类:
package generic;
import java.util.Date;
public class Test7 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// date2大于date1
Date date1 = new Date();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Date date2 = new Date();
DateInterval dateInterval = new DateInterval();
Pair<Date> datePair = dateInterval;
datePair.setFirst(date1);
datePair.setSecond(date2);
System.out.println(datePair.getFirst());
System.out.println(datePair.getSecond());
}
}
运行结果如下:
Tue Jun 09 10:25:51 CST 2015
Tue Jun 09 10:25:54 CST 2015
改动一下测试类,把first设置为date2,把second设置为date1:
package generic;
import java.util.Date;
public class Test7 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// date2大于date1
Date date1 = new Date();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Date date2 = new Date();
DateInterval dateInterval = new DateInterval();
Pair<Date> datePair = dateInterval;
datePair.setFirst(date2);
datePair.setSecond(date1);
System.out.println(datePair.getFirst());
System.out.println(datePair.getSecond());
}
}
运行结果如下:
Tue Jun 09 10:27:15 CST 2015
null
因为date1小于getFirst()的返回值date2,所以second并没有成功赋值为date1。可见,代码正常运行,
datePair.setSecond(date1);
执行的是DateInterval的setSecond(Date second)方法。
(问题1)
但这和我理解的不太一样。因为类型擦除后,Pair中的setSecond方法如下:
public void setSecond(Object newValue) { second = newValue; }
DateInterval的
setSecond方法如下:
public void setSecond(Date second) {
if (second.compareTo(getFirst()) >= 0) {
super.setSecond(second);
}
<span style="font-size:24px;"> }</span>
按照动态绑定的规则,
datePair.setSecond(date1);
执行的应该是Pari的setSecond(Object newValue)方法,因为这个方法并没有在DateInterval被重写。
(问题1解决)
原来,编译器在DateInterval中添加了这样一个桥方法:
public void setSecond(Object second) {
setSecond((Date) second);
}
这样,就可以理解为什么上面的代码能够正常执行了。
(问题2)
这里又出现了另一个问题,如果在DateInterval中也重写了getSecond方法:
public Date getSecond() {
return (Date) super.getSecond().clone();
}
为了让 DateInterval中getSecond方法与Pair中的getSecond方法一致,编译器同样会在DateInterval中添加了这样一个桥方法:
public Object getSecond() {
return getSecond();
}
这样 DateInterval中就有两个方法签名一样的getSecond方法,这是不符合Java编码规则的。
(问题2解决)
原来虚拟机通过方法签名和返回类型确定一个方法,因此,编译器可以产生方法签名相同但返回类型不同的方法字节码。这是《Java核心技术 卷1》中给出的解答。
(关于问题2的解决,我有疑问)
Pair和DateInterval中的getSecond方法有相同的方法签名,它俩的动态绑定执行能够顺序进行,为什么还要添加一个桥方法呢?(Java泛型类继承时,子类的类型变量要么和父类一样,要么是父类的子类,也就是说DateInterval中的getSecond方法的返回类型要么是Object要么是Object的子类型,它们符合“具有协变的返回类型”)。希望知道答案的朋友能给在评论中给出答案。