理解方法调用
弄清楚如何在对象上应用方法调用非常重要。假设要调用x.f(args)
,隐式参数x
声明为类C
的一个对象。下面是调用过程的详细描述:
编译器查看对象的声明类型和方法名。假设调用
x.f(param)
,且隐式参数声明为类C
的对象。需要注意的是:有可能存在多个名字为f
,但参数类型不一样的方法。例如,可能存在方法f(int)
和方法f(String)
。编译器会列举C
类中所有名为f
的方法和其超类中访问属性为public
且名为f
的方法。
至此,编译器已经获得所有可能被调用的方法。接下来,编译器将查看调用方法时所提供的参数类型。如果在所有名为
f
的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析(overloading resolution)。由于允许类型转化,所以这个过程可能比较复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。如果是
private
方法、static
方法、final
方法或者构造器,那么编译器可以准确的知道应该调用哪个方法,我们将这种调用称为静态绑定(static binding)。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定(dynamic binding)。当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与
x
所引用对象的是实际类型最合适的那个类的方法。假设x
的实际类型是D
,它是C
的子类。如果D
定义了方法f(String)
,就直接调用它;否则,将在D
的超类中寻找f(String)
,以此类推。
每次调用方法都要进行搜索,时间开销很大。因此,虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这个方法既有可能是D.f(String)
,也有可能是X.f(String)
,这里的X
是D
的超类。如果调用super.f(param)
,编译器将对隐式参数超类的方法表进行搜索。