main 方法
public class Solution {
public static void main(String[] args) {
Person person = new Person();
person.hello();
}
}
class Person {
public void hello() {
System.out.println("hello");
}
}
源文件名是 Solution.java,這是因為文件名必須與 public 類的名字相匹配。在一個源文件中,只能有一個公有類,但可以有任意數(shù)目的非公有類。
在這個示例程序中包含兩個類:Person 類和帶有 public 訪問修飾符的 Solution 類。Solution 類包含了 main 方法。
當編譯這段源代碼的時候,編譯器將在目錄下創(chuàng)建兩個 class 文件:Solution.class 和 Person.class。
將程序中包含 main 方法的類名提供給字節(jié)碼解釋器,以便啟動這個程序:java Solution。字節(jié)碼解釋器開始運行 Solution 類的 main 方法中的代碼。
理解方法調(diào)用
下面假設要調(diào)用 x.f(args)。下面是調(diào)用過程的詳細描述:
- 編譯時:
- 編譯器査看對象變量的聲明類型和方法名,然后獲得所有可能被調(diào)用的候選方法。
- 編譯器査看調(diào)用方法時提供的參數(shù)類型,然后獲得需要調(diào)用的方法名字和參數(shù)類型。
- 運行時:
- 如果方法是 private 方法、static 方法、final 方法或者構造器方法,那么編譯器將可以準確地知道應該調(diào)用哪個方法,我們將這種調(diào)用方式稱為靜態(tài)綁定(static binding)。與此對應的是,調(diào)用的方法依賴于對象變量 x 的實際類型,并且在運行時實現(xiàn)動態(tài)綁定。
- 虛擬機預先為每個類創(chuàng)建了一個方法表(method table),方法表中列出了所有方法的簽名和實際調(diào)用的方法。這樣一來,在真正調(diào)用方法的時候,虛擬機僅查找方法表就行了。
1、編譯器査看對象變量的聲明類型和方法名,然后獲得所有可能被調(diào)用的候選方法。假設調(diào)用 x.f(param),且對象變量 x 被聲明為 C 類型。需要注意的是:有可能存在多個名字為 f,但參數(shù)類型不一樣的方法。例如,可能存在方法 f(int) 和方法 f(String)。編譯器將會一一列舉所有 C 類中名為 f 的方法和其父類中訪問屬性為 public 且名為 f 的方法(父類的私有方法不可訪問)。至此,編譯器已獲得所有可能被調(diào)用的候選方法。
2、接下來,編譯器將査看調(diào)用方法時提供的參數(shù)類型,然后獲得需要調(diào)用的方法名字和參數(shù)類型。如果在所有名為 f 的方法中存在一個與提供的參數(shù)類型完全匹配,就選擇這個方法。這個過程被稱為重載解析(overloading resolution)。例如,對于調(diào)用 x.f("Hello") 來說,編譯器將會挑選 f(String),而不是 f(int)。由于允許類型轉(zhuǎn)換(int 可以轉(zhuǎn)換成 double,Manager 可以轉(zhuǎn)換成 Employee 等),所以這個過程可能很復雜。如果編譯器沒有找到與參數(shù)類型匹配的方法,或者發(fā)現(xiàn)經(jīng)過類型轉(zhuǎn)換后有多個方法與之匹配,就會報告一個錯誤。至此,編譯器已獲得需要調(diào)用的方法名字和參數(shù)類型。
如果方法是 private 方法、static 方法、final 方法或者構造器方法,那么編譯器將可以準確地知道應該調(diào)用哪個方法,我們將這種調(diào)用方式稱為靜態(tài)綁定(static binding)。與此對應的是,調(diào)用的方法依賴于對象變量 x 的實際類型,并且在運行時實現(xiàn)動態(tài)綁定。在我們列舉的示例中,編譯器采用動態(tài)綁定的方式生成一條調(diào)用 f(String) 的指令。
當程序運行,并且采用動態(tài)綁定調(diào)用方法時,虛擬機一定調(diào)用與對象變量 x 所引用的對象的實際類型最合適的那個類的方法。假設 x 的實際類型是 D,D 是 C 類的子類。如果 D 類定義了 f(String) 方法,虛擬機就直接調(diào)用 D 類的 f(String) 方法;否則(D 類中沒有定義 f(String) 方法),將在 D 類的父類中尋找 f(String),以此類推。
每次調(diào)用方法都要進行搜索,時間開銷相當大。因此,虛擬機預先為每個類創(chuàng)建了一個方法表(method table),方法表中列出了所有方法的簽名(方法名、參數(shù)類型)和實際調(diào)用的方法。這樣一來,在真正調(diào)用方法的時候,虛擬機僅查找方法表就行了。
在前面的例子中,虛擬機搜索 D 類的方法表,以便尋找與調(diào)用 f(Sting) 相匹配的方法。這個方法既有可能是 D.f(String),也有可能是 C.f(String),這里的 C 是 D 的父類。這里需要提醒一點,如果調(diào)用 super.f(param),編譯器將對對象變量父類的方法表進行搜索。
方法調(diào)用的示例
public static void main(String[] args) {
Employee e = new Manager("Carl Cracker", 80000, 1987, 12, 15);
System.out.println("salary=" + e.getSalary());
}
現(xiàn)在,查看一下調(diào)用 e.getSalary() 的詳細過程。對象變量 e 被聲明為 Employee 類型。Employee 類只有一個名叫 getSalary() 的方法,這個方法沒有參數(shù)。因此,在這里不必擔心重載解析的問題。
由于 getSalary() 不是 private 方法、static 方法,也不是 final 方法,所以將采用動態(tài)綁定。虛擬機為 Employee 和 Manager 兩個類生成方法表。
在 Employee 的方法表中,列出了這個類定義的方法。實際上,下面列出的方法并不完整,Employee 類有一個父類 Object,Employee 類從這個父類中還繼承了許多方法,在此我們略去了 Object 的方法。
Employee:
getName() -> Employee.getName()
getSalary() -> Employee.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(doubl e)
Manager 的方法表稍微有些不同。其中有三個方法是繼承而來的,一個方法是重新定義的(方法的重寫),還有一個方法是新增加的。
Manager:
getName() -> Employee.getName()
getSalary() -> Manager.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(doubl e)
setBonus(double) -> Manager.setBonus(double)
在運行時,調(diào)用 e.getSalary() 的解析過程為:
- 首先,虛擬機提取對象變量 e 的實際類型的方法表。既可能是 Employee、Manager 的方法表,也可能是 Employee 類的其他子類的方法表。
- 接下來,虛擬機搜索對象變量 e 的實際類型的方法表。此時,虛擬機已經(jīng)知道應該調(diào)用哪個方法。
- 最后,虛擬機調(diào)用方法。
動態(tài)綁定有一個非常重要的特性:無需對現(xiàn)存的代碼進行修改,就可以對程序進行擴展。假設增加一個新類 Executive,并且對象變量 e 有可能引用這個類的對象,我們不需要對包含調(diào)用 e.getSalary() 的代碼進行重新編譯。如果 e 恰好引用一個 Executive 類的對象,就會自動地調(diào)用 Executive.getSalary() 方法。文章來源:http://www.zghlxwxcb.cn/news/detail-421351.html
參考資料
《Java核心技術卷一:基礎知識》(第10版)第 5 章:繼承 5.1.6 理解方法調(diào)用文章來源地址http://www.zghlxwxcb.cn/news/detail-421351.html
到了這里,關于理解Java程序的執(zhí)行的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!