1.重載(overload)方法
對重載方法的調用主要看靜态類型,靜态類型是什麼類型,就調用什麼類型的參數方法。
2.重寫(override)方法
對重寫方法的調用主要看實際類型。實際類型如果實作了該方法則直接調用該方法,如果沒有實作,則在繼承關系中從低到高搜尋有無實作。
3.
java檔案的編譯過程中不存在傳統編譯的連接配接過程,一切方法調用在class檔案中存放的隻是符号引用,而不是方法在實際運作時記憶體布局中的入口位址。
基本概念
1.靜态類型與實際類型,方法接收者
1 Human man = new Man();
2 man.foo();
上面這條語句中,man的靜态類型為Human,實際類型為Man。所謂方法接收者,就是指将要執行foo()方法的所有者(在多态中,有可能是父類Human的對象,也可能是子類Man的對象)。
2.位元組碼的方法調用指令
(1)invokestatic:調用靜态方法
(2)invokespecial:調用執行個體構造器方法,私有方法和父類方法。
(3)invokevirtual:調用所有的虛方法。
(4)invokeinterface:調用接口方法,會在運作時再确定一個實作此接口的對象。
(5)invokedynamic:先在運作時動态解析出調用點限定符所引用的方法,然後再執行該方法。
前2條指令(invokestatic, invokespecial),在類加載時就能把符号引用解析為直接引用,符合這個條件的有靜态方法、執行個體構造器方法、私有方法、父類方法這4類,這4類方法叫非虛方法。
非虛方法除了上面靜态方法、執行個體構造器方法、私有方法、父類方法這4種方法之外,還包括final方法。雖然final方法使用invokevirtual指令來調用,但是final方法無法被覆寫,沒有其他版本,無需對方法接收者進行多态選擇,或者說多态選擇的結果是唯一的。
重載overload
重載隻會發生在編譯期,即編譯器時jvm可以通過靜态類型确定符号引用所對應的直接引用。
上面說的靜态類型和動态類型都是可以變化的。靜态類型發生變化(強制類型轉換)時,對于編譯器是可知的,即編譯器知道對象的最終靜态類型。而實際類型變化(對象指向了其他對象)時,編譯器是不可知的,隻有在運作時才可知。
1 //靜态類型變化
2 sr.sayHello((Man) man);
3 sr.sayHello((Woman) man);
4 //實際類型變化
5 Human man = new Man();
6 man = new Woman();
重載隻涉及靜态類型的選擇。
測試代碼如下:
1 /**
2 * Created by fan on 2016/3/28.
3 */
4 public class StaticDispatcher {
5
6 static class Human {}
7 static class Man extends Human {}
8 static class Woman extends Human {}
9
10 public void sayHello(Human human) {
11 System.out.println("Hello guy!");
12 }
13
14 public void sayHello(Man man) {
15 System.out.println("Hello man!");
16 }
17
18 public void sayHello(Woman woman) {
19 System.out.println("Hello woman!");
20 }
21
22 public static void main(String[] args) {
23 StaticDispatcher staticDispatcher = new StaticDispatcher();
24 Human man = new Man();
25 Human woman = new Woman();
26 staticDispatcher.sayHello(man);
27 staticDispatcher.sayHello(woman);
28 staticDispatcher.sayHello((Man)man);
29 staticDispatcher.sayHello((Woman)man);
30 }
31 }
先看看執行結果:

由此可見,當靜态類型發生變化時,會調用相應類型的方法。但是,當将Man強制類型轉換成Woman時,沒有編譯錯誤,卻有運作時異常。“classCastException”類映射異常。
看看位元組碼指令:
javap -verbose -c StaticDispatcher
1 public static void main(java.lang.String[]);
2 Code:
3 Stack=2, Locals=4, Args_size=1
4 0: new #7; //class StaticDispatcher
5 3: dup
6 4: invokespecial #8; //Method "<init>":()V
7 7: astore_1
8 8: new #9; //class StaticDispatcher$Man
9 11: dup
10 12: invokespecial #10; //Method StaticDispatcher$Man."<init>":()V
11 15: astore_2
12 16: new #11; //class StaticDispatcher$Woman
13 19: dup
14 20: invokespecial #12; //Method StaticDispatcher$Woman."<init>":()V
15 23: astore_3
16 24: aload_1
17 25: aload_2
18 26: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V
19 29: aload_1
20 30: aload_3
21 31: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V
22 34: aload_1
23 35: aload_2
24 36: checkcast #9; //class StaticDispatcher$Man
25 39: invokevirtual #14; //Method sayHello:(LStaticDispatcher$Man;)V
26 42: aload_1
27 43: aload_2
28 44: checkcast #11; //class StaticDispatcher$Woman
29 47: invokevirtual #15; //Method sayHello:(LStaticDispatcher$Woman;)V
30 50: return
看到,在強制類型轉換時,會有指令checkCast的調用,而且invokevirtual指令的調用方法也發生了變化
39: invokevirtual #14; //Method sayHello:(LStaticDispatcher$Man;)V
。
虛拟機(準确說是編譯器)在重載時是通過參數的靜态類型而不是實際類型作為判定依據的。
對于字面量類型,編譯器會自動進行類型轉換。轉換的順序為:
char-int-long-float-double-Character-Serializable-Object
轉換成Character是因為發生了自動裝箱,轉換成Serializable是因為Character實作了Serializable接口。
重寫override
重寫發生在運作期,在運作時jvm會先判斷對象的動态類型,而後根據對象的動态類型選擇對應vtable,進而根據符号引用找到對應的直接引用。
如:
BaseClass c = new ChildClass();
則c能通路的函數清單為Method1,Method2,即黃色部分。
1 /**
2 * Created by fan on 2016/3/29.
3 */
4 public class DynamicDispatcher {
5
6 static abstract class Human {
7 protected abstract void sayHello();
8 }
9
10 static class Man extends Human {
11
12 @Override
13 protected void sayHello() {
14 System.out.println("Man say hello");
15 }
16 }
17
18 static class Woman extends Human {
19
20 @Override
21 protected void sayHello() {
22 System.out.println("Woman say hello");
23 }
24 }
25
26 public static void main(String[] args) {
27 Human man = new Man();
28 Human woman = new Woman();
29 man.sayHello();
30 woman.sayHello();
31 man = new Woman();
32 man.sayHello();
33 }
34
35 }
執行結果:
看下位元組碼指令:
1 public static void main(java.lang.String[]);
2 Code:
3 Stack=2, Locals=3, Args_size=1
4 0: new #2; //class DynamicDispatcher$Man
5 3: dup
6 4: invokespecial #3; //Method DynamicDispatcher$Man."<init>":()V
7 7: astore_1
8 8: new #4; //class DynamicDispatcher$Woman
9 11: dup
10 12: invokespecial #5; //Method DynamicDispatcher$Woman."<init>":()V
11 15: astore_2
12 16: aload_1
13 17: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V
14 20: aload_2
15 21: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V
16 24: new #4; //class DynamicDispatcher$Woman
17 27: dup
18 28: invokespecial #5; //Method DynamicDispatcher$Woman."<init>":()V
19 31: astore_1
20 32: aload_1
21 33: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V
22 36: return
從位元組碼中可以看到,他們調用的都是相同的方法
invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V
,但是執行的結果卻顯示調用了不同的方法。因為,在編譯階段,編譯器隻知道對象的靜态類型,而不知道實際類型,是以在class檔案中隻能确定要調用父類的方法。但是在執行時卻會判斷對象的實際類型。如果實際類型實作這個方法,則直接調用,如果沒有實作,則按照繼承關系從下往上一次檢索,隻要檢索到就調用,如果始終沒有檢索到,則抛異常(難道能編譯通過)。
(1)測試代碼如下:
1 /**
2 * Created by fan on 2016/3/29.
3 */
4 public class Test {
5
6 static class Human {
7 protected void sayHello() {
8 System.out.println("Human say hello");
9 }
10 protected void sayHehe() {
11 System.out.println("Human say hehe");
12 }
13 }
14
15 static class Man extends Human {
16
17 @Override
18 protected void sayHello() {
19 System.out.println("Man say hello");
20 }
21
22 // protected void sayHehe() {
23 // System.out.println("Man say hehe");
24 // }
25 }
26
27 static class Woman extends Human {
28
29 @Override
30 protected void sayHello() {
31 System.out.println("Woman say hello");
32 }
33
34 // protected void sayHehe() {
35 // System.out.println("Woman say hehe");
36 // }
37 }
38
39 public static void main(String[] args) {
40 Human man = new Man();
41 man.sayHehe();
42 }
43
44 }
測試結果如下:
位元組碼指令:
1 public static void main(java.lang.String[]);
2 Code:
3 Stack=2, Locals=2, Args_size=1
4 0: new #2; //class Test$Man
5 3: dup
6 4: invokespecial #3; //Method Test$Man."<init>":()V
7 7: astore_1
8 8: aload_1
9 9: invokevirtual #4; //Method Test$Human.sayHehe:()V
10 12: return
位元組碼指令與上面代碼的位元組碼指令沒有本質差別。
(2)測試代碼如下:
1 /**
2 * Created by fan on 2016/3/29.
3 */
4 public class Test {
5
6 static class Human {
7 protected void sayHello() {
8 }
9 }
10
11 static class Man extends Human {
12
13 @Override
14 protected void sayHello() {
15 System.out.println("Man say hello");
16 }
17
18 protected void sayHehe() {
19 System.out.println("Man say hehe");
20 }
21 }
22
23 static class Woman extends Human {
24
25 @Override
26 protected void sayHello() {
27 System.out.println("Woman say hello");
28 }
29
30 protected void sayHehe() {
31 System.out.println("Woman say hehe");
32 }
33 }
34
35 public static void main(String[] args) {
36 Human man = new Man();
37 man.sayHehe();
38 }
39
40 }
編譯時報錯:
這個例子說明了:Java編譯器是基于靜态類型進行檢查的。
修改上面錯誤代碼,如下所示:
1 /**
2 * Created by fan on 2016/3/29.
3 */
4 public class Test {
5
6 static class Human {
7 protected void sayHello() {
8 System.out.println("Human say hello");
9 }
10 // protected void sayHehe() {
11 // System.out.println("Human say hehe");
12 // }
13 }
14
15 static class Man extends Human {
16
17 @Override
18 protected void sayHello() {
19 System.out.println("Man say hello");
20 }
21
22 protected void sayHehe() {
23 System.out.println("Man say hehe");
24 }
25 }
26
27 static class Woman extends Human {
28
29 @Override
30 protected void sayHello() {
31 System.out.println("Woman say hello");
32 }
33
34 protected void sayHehe() {
35 System.out.println("Woman say hehe");
36 }
37 }
38
39 public static void main(String[] args) {
40 Man man = new Man();
41 man.sayHehe();
42 }
43
44 }
注意在Main方法中,改成了
Man man = new Man();
執行結果如下所示:
位元組碼指令如下所示:
1 public static void main(java.lang.String[]);
2 Code:
3 Stack=2, Locals=2, Args_size=1
4 0: new #2; //class Test$Man
5 3: dup
6 4: invokespecial #3; //Method Test$Man."<init>":()V
7 7: astore_1
8 8: aload_1
9 9: invokevirtual #4; //Method Test$Man.sayHehe:()V
10 12: return
注意上面的位元組碼指令
invokevirtual #4; //Method Test$Man.sayHehe:()V
。