弄清調用對象方法的執行過程十分重要。下面是調用過程的詳細描述:java
1) 編譯器查看對象的聲明類型和方法名。假設調用x.f(param),且隱式參數x聲明爲C類的對象。須要注意的是:有可能存在多個名爲f,但參數類型不同的方法。例如,可能存在方法f(int)和方法f(String)。編譯器將會 一 一列舉全部C類中名爲f的方法和其超類中訪問屬性爲public且名爲f的方法。spa
至此,編譯器已得到全部可能被調用的候選方法。code
2) 接下來,編譯器將查看調用方法時提供的參數類型。若是在全部名爲f的方法中存在一個與提供的參數類型徹底匹配,就選擇這個方法。這個過程被稱爲重載解析(overloading resolution)。例如,對於調用x.f("Hello")來講,編譯器將會挑選f(String),而不是f(int)。因爲容許類型轉換(int能夠轉換成double,Manager能夠轉換成Employee,等等),因此這個過程可能很複雜。若是編譯器沒有找到與參數類型匹配的方法,或者發現通過類型轉換後有多個方法與之匹配,就會報告一個錯誤。orm
至此,編譯器已得到須要調用的方法名字和參數類型。對象
√ 註釋:前面曾經說過,方法的名字和參數列表稱爲方法的簽名。例如,f(int)和f(String)是兩個具備相同名字,不一樣簽名的方法。若是在子類中定義了一個與超類簽名相同的方法,那麼子類中的這個方法就覆蓋了超類中的這個相同簽名的方法。blog
不過,返回類型不是簽名的一部分,所以,在覆蓋方法時,必定要保證返回類型的兼容性。在Java SE 5.0之前的版本中,要求返回類型必須是同樣的。如今容許子類將覆蓋方法的返回類型定義爲原返回類型的子類型。例如,假設Employee類有繼承
public Employee getBuddy() { ... }ip
在後面的子類Manager中,能夠按照以下所示的方式覆蓋這個方法get
public Manager getBuddy() { ... }編譯器
咱們說,這兩個getBuddy方法具備可協變的返回類型 (covariant return types) 。
3) 若是是private方法、static方法、final方法 或者構造器,那麼編譯器將能夠準確地知道應該調用哪一個方法,咱們將這種調用方式稱爲靜態綁定 (static binding)。於此對應的是,調用的方法依賴於隱式參數的實際類型,而且在運行時實現動態綁定(dynamic binding)。在咱們列舉的示例中,編譯器採用動態綁定的方式生成一條調用f(String)的指令。
4) 當程序運行,而且採用動態綁定調用方法時,虛擬機必定調用與x所引用對象的實際類型最合適的那個類的方法。假設x的實際類型是D,它是C類的子類。若是D類定義了方法f(String),就直接調用它;不然,將在D類的超類中尋找f(String),以此類推。
每次調用方法都要進行搜素,時間開銷至關大。所以,虛擬機預先爲每一個類建立了一個方法表 (method table) ,其中列出了全部方法的簽名和實際調用的方法。這樣一來,在真正調用方法的時候,虛擬機僅查找這個表就好了。在前面的例子中,虛擬機搜索D類的方法表,以便尋找與調用f(String) 相匹配的方法。這個方法既有多是D.f(String),也有多是X.f(String),這裏的X是D的超類。這裏須要提醒一點,若是調用super.f(param),編譯器將對隱式參數超類的方法表進行搜索。
如今,查看查看一下例5-1中調用e.getSalary()的詳細過程。e聲明爲Employee類型。Employee類只有一個名叫getSalary的方法,這個方法沒有參數。所以,在這裏沒必要擔憂重載解析的問題。
因爲getSalary不是private方法、static方法或final方法,因此將採用動態綁定。虛擬機爲Employee和Manager兩個類生成方法表。在Employee的方法表中,列出了這個類定義的全部方法:
Employee:
getName() -> Employee.getName()
getSalary() -> Employee.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(double)
實際上,上面列出的方法並不完整,在此咱們略去了從Object繼承來的許多方法。
Manager方法表稍微有些不一樣。其中有三個方法是繼承而來的,一個方法是從新定義的,還有一個方法是新增長的。
Manager:
getName() -> Employee.getName()
getSalary() -> Manager.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(double)
setBonus(double) -> Manager.setBonus(double)
在運行的時候,調用e.getSalary()的解析過程爲:
1) 首先,虛擬機提取e的實際類型的方法表。既多是Employee、Manager的方法表,也多是Employee類的其它子類的方法表。
2) 接下來,虛擬機搜索定義getSalary簽名的類。此時,虛擬機已經知道應該調用哪一個方法。
3) 最後,虛擬機調用方法。
動態綁定有一個很是重要的特性:無需對現存的代碼進行修改,就能夠對程序進行擴展。假設增長一個新類Executive,而且變量e有可能引用這個類的對象,咱們不須要對包含調用e.getSalary() 的代碼進行從新編譯。若是e剛好引用一個Executive類的對象,就會自動地調用Executive.getSalary() 方法。
× 警告:在覆蓋一個方法的時候,子類方法不能低於超類方法的可見性。特別是,若是超類方法是public,子類方法必定要聲明爲public。常常會發生這類錯誤:在聲明子類方法的時候,遺漏了public修飾符。此時,編譯器將會把它解釋爲試圖下降訪問權限。
例5-1 ManagerTest.java
import java.util.*; /** * This program demonstrates inheritance. * @version 1.21 2004-02-21 * @author Cay Horstmann */ public class ManagerTest { public static void main(String[] args) { // construct a Manager object Manager boss = new Manager("Carl Craker",80000,1987,12,15); boss.setBonus(5000); Employee[] staff = new Employee[3]; // fill the staff array with Manager and Employee objects staff[0] = boss; staff[1] = new Employee("Harry Hacker",50000,1989,10,1); staff[2] = new Employee("Tommy Tester",40000,1990,3,15); // print out information about all Employee objects for(Employee e:staff) System.out.println("name: " + e.getName() + ",salary: " + e.getSalary()); } } class Employee { public Employee(String n,double s,int year,int month,int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year,month-1,day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent /100; salary += raise; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { /** * @param n the employee's name * @param s the salary * @param year the hire year * @param month the hire month * @param day the hire day */ public Manager(String n,double s,int year,int month,int day) { super(n,s,year,month,day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } private double bonus; }
—— 聲明:此文源自《JAVA 核心技術》5.1.3 "動態綁定"