在[#0x0009]里面说过,“除了static方法和final方法(final包含private)外,Java对其他所有的方法都采用dynamic binding”(P151, Chapter 8, Thinking in Java, Fourth Edition),不过下面的这个例子也许会让人有点吃惊(adapted from Chapter 8, Thinking in Java, Fourth Edition):
//@file RTTI.java
//RTTI: Run-Time Type Identification
class Useful
{
public void f() {System.out.println("Useful.f()");}
}
class MoreUseful extends Useful
{
public void f() {System.out.println("MoreUseful.f()");}
public void g() {System.out.println("MoreUseful.g()");}
}
public class RTTI
{
public static void main(String[] args)
{
Useful x = new MoreUseful();
x.f();
//x.g();//编译错误:找不到符号
((MoreUseful)x).g(); // Downcast/RTTI
}
}
//output:
/*
MoreUseful.f()
MoreUseful.g()
*/
按理来说,x.f()通过动态绑定能够正确调用MoreUseful的f()方法,那么为什么x.g()就不行呢?真的是“除了static方法和final方法(final包含private)外,Java对其他‘所有’的方法都采用dynamic binding”吗?还是只对覆写方法才动态绑定?
其实这里涉及到动态绑定的细节问题。当然,的确是“除了static方法和final方法(final包含private)外,Java对其他所有的方法都采用dynamic binding”,只不过在使用动态绑定之前,编译器还做了一些其他的工作,而这些工作,就是造成上面代码结果的原因。
Java的方法调用过程:
->编译器查看引用(x)的声明类型(Useful)和方法名(g());
-->通过声明类型找到方法列表;
---->如果方法名不在方法列表中,则编译器报错(g()不在Useful的方法列表里,所以出错);
---->如果方法名在方法列表中,则继续下列步骤;
->编译器查看方法的参数列表,获取参数方法签名;
-->如果方法是private、static、final或者构造器,编译器就可以确定调用那个方法(这是静态绑定);
-->如果不是上述情况,就要使用动态绑定;
可见,x.g()出错是由于使用动态绑定前的方法名检查未通过。从这个角度来说,动态绑定似乎的确只适用于覆写方法。
由于无法使用动态绑定,所以要正确调用x.g()方法,向下转型((MoreUseful)x)就必不可少了。这里x必须指向一个实际的MoreUseful对象(即是通过向上转型得来),如果Useful x = new Useful(),那么((MoreUseful)x)会编译报错。