Java 調用對象方法的執行過程

    弄清調用對象方法的執行過程十分重要。下面是調用過程的詳細描述: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 "動態綁定"

相關文章
相關標籤/搜索