編譯時類型和運行時類型:
Java的引用變量有兩個類型,一個是編譯時類型,一個是運行時類型,編譯時類型由聲明該變量時使用的類型決定,運行時類型由實際賦給該變量的對象決定。如果編譯時類型和運行時類型不一致,會出現所謂的多態。因爲子類其實是一種特殊的父類,因此java允許把一個子類對象直接賦值給一個父類引用變量,無須任何類型轉換,或者被稱爲向上轉型,由系統自動完成。
引用變量在編譯階段只能調用其編譯時類型所具有的方法,但運行時則執行它運行時類型所具有的方法,因此,編寫Java代碼時,引用變量只能調用聲明該變量所用類裏包含的方法。與方法不同的是,對象的屬性則不具備多態性。通過引用變量來訪問其包含的實例屬性時,系統總是試圖訪問它編譯時類所定義的屬性,而不是它運行時所定義的屬性。
前期綁定和後期綁定(動態綁定、運行時綁定):
綁定:將一個方法調用同方法主體關聯起來叫做綁定
前期綁定:在程序執行之前進行綁定(如果有的話,由編譯器和連接器完成),前期綁定是面向過程程序設計語言中默認的綁定方式,例如,C語言只有一種方法調用,那就是前期綁定。
後期綁定:就是在程序運行時根據對象的類型進行綁定,也叫作動態綁定或運行時綁定。
注意:Java中除了static和final方法(private方法屬於final方法,因爲類中的private方法被隱式指定爲final方法)之外,其他方法都是後期綁定。這意味着通常不必判定是否該進行後期綁定,因爲它是自動發生的。
下面舉例說明:
1.子類方法覆蓋父類方法( 子類重寫父類中的方法,調用子類中的方法)
- class Father{
- public void method(){
- System.out.println("父類方法:"+this.getClass());
- }
- }
- public class Son extends Father{
- public void method(){
- System.out.println("子類方法:"+this.getClass());
- }
- public static void main(String[] args){
- Father instance = new Son();
- instance.method();
- }
- }
- 運行結果:<pre name="code" class="java" style="font-size:14px; line-height:26px"><pre name="code" class="java" style="font-size:14px; line-height:26px">子類方法:class Son</pre><br></pre>
- class Father{
- public void method(){
- System.out.println("父類方法:"+this.getClass());
- }
- }
- public class Son extends Father{
- <span style="white-space:pre"> </span>public static void main(String[] args){
- Father instance = new Son();
- instance.method();
- }
- }
- 運行結果: 父類方法: class Son
3.動態綁定只是針對對象的方法,對於屬性無效。因爲屬性不能被重寫。
- class Father{
- public String name = "Father'name";
- }
- public class Son extends Father{
- public String name = "Son'name";
- public static void main(String[] args){
- Father instance = new Son();
- System.out.println(instance.name);
- }
- }
- 運行結果:<pre name="code" class="java" style="color:rgb(51,51,51); line-height:23px">Father'name</pre>
- public String name = "Father'name";
- private String name = "Father'name";
- 那麼編譯器將報錯: 錯誤: name可以在Father中訪問private
- <span style="white-space:pre"> </span>System.out.println(instance.name);
- 說明在<pre name="code" class="java" style="color:rgb(51,51,51); line-height:23px"> System.out.println(instance.name);這行代碼執行時,訪問的是父類的 name屬性,而該屬性被聲明爲private,所以無法訪問,因而報錯!</pre>
下面在分析一個例子:
- class A
- {
- int count = 20;
- }
- class B extends A
- {
- int count = 200;
- }
- public class Test
- {
- public static void main(String[] args)
- {
- A a = new A();
- System.out.println(a.count);
- B b = new B();
- System.out.println(b.count);
- A ab = b; //向上轉型
- System.out.println(ab.count);
- }
- }
- 運行結果 :20
- 200
- 20
前兩行的輸出毫無疑問,問題在
A ab = b;
System.out.println(ab.count);
的輸出是20,而不是200;在這之間我們可以用
System.out.println(ab == b);
來進行簡單的判斷,結果輸出爲 true ,說明 ab 和 b 兩個引用變量指向同一個實例,既然 ab 和 b 指向同一個實例,爲什麼輸出的是20不是200呢?原因在於:
1.對於 class A 和class B來說,class B是class A的子類,由於子類的變量並不會覆蓋父類的變量,所以實際上在class B中是存在來兩個count,在這分別記作 A.count 和B.count ;
2.雖然在 class B中存在A.count 和B.count ,但是究竟輸出那一個 count ,取決於該引用變量的聲明時類型(本文開頭紅色文字部分已經說明),此處 聲明時類型 是 class A,所以輸出 20 即A.count ,同理若改爲 B ab = b ;則輸出 200 即 B.count ;