理解方法调用
调用过程描述
1、 编译器查看对象的声明类型和方法名。假设调用 x.f(param) ,且隐式参数声明为类 C 的对象。需注意的是:有可能存在多个名字为 f ,但参数类型不一样的方法。例如,可能存在方法 f(int) 和方法 f(String) 。编译器会列举 C 类中所有名为f的方法和其超类中访问属性为public且名为f的方法。编译器会列举所有 C 类中名为 f 的的方法和它超类中属性为 public 并且名为 f 的方法,私有方法不可访问。
至此,编译器已经获得所有可能被调用的方法。
2、 编译器将查看调用方法时所提供的参数类型。如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为 重载解析 (overloading resolution) 。并且允许类型转化。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
至此,编译器已获得需调用的方法名和参数类型。
3、 如果是 private 方法、static 方法、final 方法或者构造器,那么编译器可以准确的知道该调用哪个方法,我们将这种调用称为 静态绑定 ( static binding ) 。与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。
4、 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与 x 所引用对象的是实际类型最合适的那个类的方法。假设x的实际类型是 D ,它是 C 的子类。如果 D 定义了方法 f (String) ,就直接调用它;否则,将在D的超类中寻找 f (String) ,以此类推。
由于每次调用方法都要进行搜索,很浪费时间,所以虚拟机预先为每个类创建了一个 方法表 ( method table ),其中列出了所有方法的签名和实际调用的方法,用时虚拟机直接查找这个类的表就可以了。
运行调用 e.getSalary() 的解析过程:
1、虚拟机提取 e 实际类型的方法表,可能是超类的,也可能是其他子类的。
2、虚拟机搜索定义 getSalary 签名的类,虚拟机在这个时候已经知道要调用的方法了。
3、调用方法。
动态绑定的一个重要特性:无需对现存代码进行修改就可以对程序进行扩展。比如 引入一个新类,并不需要对 e.getSalary() 的代码重新编译,当 e 恰好引入这个新类的一个对象时,这个对象会自动调用 getSalary 方法。
在覆盖一个方法时,子类方法不能低于超类方法的可见性,比如,超类方法是 public ,子类方法一定要声明为 public 。