下面几个类用于确定今天晚餐要喝的酒以及酒的温度
class Sommelier {
Wine recommend(String meal) { }
}
abstract class Wine {
// 推荐酒的温度
abstract float temperature();
}
class RedWine extends Wine {
// 红酒的温度通常略高于白酒
float temperature() { return ; }
}
class WhiteWine extends Wine {
float temperature() { return ; }
}
class Bordeaux extends RedWine {
float temperature() { return ; }
}
class Riesling extends WhiteWine {
// 继承WhiteWine类的温度
}
下面的例子利用上面的类推荐一种酒
void example() {
Wine wine = sommelierrecommend(duck);
float temp = winetemperature();
}
example的第二个调用中对于wine对象我们唯一可以肯定的是它是一个Wine但可以是Bordeaux也可以是Riesling或其他另外我们可以肯定wine对象不可能是Wine类本身的实例因为Wine类是一个抽象类编译源代码源代码中的winetemperature()调用将变成invokevirtual Wine/temperature ()F(class文件实际包含的是该文本表示形式的二进制代码这种文本化的指令描述方法称为Oolong方法)它表示的是一个方法调用——一个普通的(虚拟)方法调用而不是一个静态调用它调用的方法是Wine对象的temperature右边的()F参数称为签名(signature)()F这个签名中的空括号表示方法不需要输入参数F表示返回值是一个浮点数
JVM执行到该语句时它调用的不一定是Wine定义的temperature方法实际上在本例中JVM不可能调用Wine定义的temperature方法因为该temperature方法是一个虚拟方法JVM首先检查该对象所属的类寻找一个符合invokevirtual语句指定的名称签名特征的方法如果找不到则检查该类的超类然后是超类的超类直至找到一个合适的方法实现为止
在本例中如果实际创建的对象是一个Bordeaux则JVM调用Bordeaux类定义的temperature()F该temperature()F方法将返回如果对象是一个RieslingJVM在Riesling类中找不到适当的方法所以继续查找WhiteWine类在WhiteWine类中找到了一个合适的temperature()F方法该方法的返回值是
因此查找可用方法的过程就是沿着类的继承树通过字符串匹配寻找合适方法的过程了解这一原理有助于理解哪些修改不至于影响二进制兼容性
首先重新排列类里面的方法显然不会影响到二进制兼容性——这在C++程序中一般是不允许的因为C++程序利用数值性偏移量而不是名称来确定要调用的方法延迟绑定的关键优势正是在此如果Java也使用方法在类里面的偏移量来确定要调用的方法必然极大地限制二进制兼容机制的发挥即使极小的改动也可能导致大量的代码需要重新编译
·说明也许有人会认为C++的处理方式要比Java的快理由是根据数值性偏移量寻找方法肯定要比字符串匹配快这种说法有一定道理但只说明了类刚刚装入时的情况此后Java的JIT编译器处理的也是数值性偏移量而不再靠字符串匹配的办法寻找方法因为类装入内存之后不可能再改变所以这时的JIT编译器根本无须顾虑到二进制兼容问题因此至少在方法调用这一点上Java没有理由一定比C++慢
其次还有很重要的一点是不仅仅编译时需要检查类的继承关系而且运行时JVM还要检查类的继承关系