多態和覆蓋
多態是面向對象編程中最為重要的概念之一,而覆蓋又是體現多態最重要的方面。對於像c#和java這樣的面向對象編程的語言來說,實現了在編譯時只檢查接口是否具備,而不需關心最終的實現,即最終的實現方式是在運行時才會決定。這給強類型語言提供了強大的靈活性,請看下面的例子:
1 using System; 2 3 namespace study00 4 { 5 class Person 6 { 7 public string Name { set; get; } 8 public virtual void sayHello() 9 { 10 Console.WriteLine("Hello. My Name is " + Name); 11 } 12 } 13 14 class Student :Person 15 { 16 public override void sayHello() 17 { 18 Console.WriteLine("Hello, i am a student. My Name is " + Name); 19 } 20 } 21 22 class Teacher :Person 23 { 24 public override void sayHello() 25 { 26 Console.WriteLine("Hello, i am a teacher. My Name is " + Name); 27 } 28 } 29 30 class Program 31 { 32 static void Main(string[] args) 33 { 34 Teacher t = new Teacher(); 35 t.Name = "XiaoMing"; 36 doSomething(t); 37 38 Student s = new Student(); 39 s.Name = "XiaoMing"; 40 doSomething(s); 41 } 42 43 static void doSomething(Person p) 44 { 45 p.sayHello(); 46 } 47 } 48 49 /* Output: 50 * Hello, i am a teacher. My Name is XiaoMing 51 * Hello, i am a student. My Name is XiaoMing 52 */ 53 }
在上面這段代碼中,doSomething方法需要一個Person類型的對象,但是即使給他傳遞的是Student類型的對象和Teacher類型的對象,她也能夠欣然接受,並根據響應的類型作出正確的處理。我們都知道,這是由於Student和Teacher都繼承了Person類,當調用doSomething傳遞參數時,Student類型的對象發生了向上轉型,也稱為里氏替換,即父類能夠出現的地方,子類一定能夠進行替換。所以將Student類型的對象傳遞給doSomething方法,編譯不會出錯,而且doSomething方法能夠正確執行。這便是面向對象給我們提供的便利,即我們不需要判斷某個對象屬於什么類型,只要他能夠滿足給定的要求(這里是繼承了Person類),就能夠正確運行。
實際上,從更抽象的角度來看,由於每一個類都可以看做某個接口的具體實現,Person可以看做是包含了sayHello()方法的接口的實現。那么我們可以說,Person與它的子類Student和Teacher都是同一個接口的實現,那么我們也就可以理解,doSomething()方法可以正確的處理實現了sayHello()方法的類型。對於doSomething()方法,他不需要知道傳遞過來的參數是Person類的對象還是它的子類,只要該對象實現了sayHello()方法(繼承機制使得子類自動繼承了父類的所有方法),那么就可以正確執行。
在上面的代碼中,Person中已經有了相應的sayHello()方法的實現,但是當我們運行時卻發現,程序運行的卻是它的子類的同名方法。這就是所謂的覆蓋,即子類隱藏了父類中的同名方法,通過覆蓋Person的sayHello()方法,Person的子類實現了自己想要實現的方法,同時又可以向上轉型為父類去參與doSomething()方法的運行。也就是說,通過覆蓋父類的同名方法,程序實現了運行時的多態。
virtual和override關鍵字
實現多態時,用到了兩個關鍵字,virtual和override,分別用在父類和子類的簽名相同的方法中。什么是簽名相同呢?簽名相同就是方法同名、參數相同(個數相同、類型相同)、返回值相同。virtual用在父類方法中,表示該方法可以被覆蓋。override用在子類的同簽名方法中,表示重寫了父類的同簽名方法。override關鍵字修飾的的方法必須是和父類的方法是簽名相同,而且父類的簽名相同的方法必須是被virtual、abstract或者override關鍵字修飾。
new關鍵字
我們都知道在c#或java中,創建一個新的對象可以使用new關鍵字。而在c#中,new還有另一種用法,那就是作為方法的修飾符。new作為方法的修飾符起到了隱藏父類簽名相同的方法,在某些情況下起到了和override關鍵字相同的效果,請看下面的代碼:
1 using System; 2 3 namespace study001 4 { 5 class Person 6 { 7 public string Name { set; get; } 8 public virtual void sayHello() 9 { 10 Console.WriteLine("Hello. My Name is " + Name); 11 } 12 public void sayBey() 13 { 14 Console.WriteLine("Bey"); 15 } 16 } 17 18 class Student : Person 19 { 20 public override void sayHello() 21 { 22 Console.WriteLine("Hello, i am a student. My Name is " + Name); 23 } 24 public new void sayBey() 25 { 26 Console.WriteLine("Bey, don't forget me!"); 27 } 28 } 29 30 class OverrideAndNew 31 { 32 static void Main(string[] args) 33 { 34 Console.WriteLine("********demo1*********"); 35 Student s1 = new Student(); 36 s1.Name = "XiaoMing"; 37 s1.sayHello(); 38 s1.sayBey(); 39 40 Console.WriteLine("********demo2*********"); 41 Person p = new Student(); 42 p.Name = "LiHua"; 43 p.sayHello(); 44 p.sayBey(); 45 46 Console.WriteLine("********demo3*********"); 47 Student s2 = new Student(); 48 Person p2 = s2; 49 p2.Name = "LaoWang"; 50 p2.sayHello(); 51 p2.sayBey(); 52 } 53 } 54 55 /* Output: 56 * ********demo1********* 57 * Hello, i am a student. My Name is XiaoMing 58 * Bey, don't forget me! 59 * ********demo2********* 60 * Hello, i am a student. My Name is LiHua 61 * Bey 62 * ********demo3********* 63 * Hello, i am a student. My Name is LaoWang 64 * Bey 65 */ 66 }
在上面代碼中的第一個示例中,Student類繼承了Person類,並且覆蓋了Person類的sayHello()方法,而對於父類中的sayBey()方法,子類中有對應的簽名相同的方法,但是沒有采用override關鍵字修飾,而是采用了new關鍵詞修飾,並且父類的對應方法沒有采用任何的關鍵字修飾。在這種情況下,從輸出結果中可以看出,子類的對象也成功隱藏了父類的sayBey()方法,采用了自己的實現。這樣的情況下,只需要子類的同名方法用new關鍵字修飾即可,貌似比override更加方便。而且更加重要的是,這個new關鍵字是可以省略的,也就是說默認情況下,子類的同名方法會隱藏父類的方法。
而從上面的第二個和第三個實例中我們就可以看到new關鍵字和override關鍵字的不同之處了。在第二個示例中,采用了父類類型Person的變量引用了子類類型Student的對象。在這種情況下,雖然被覆蓋的sayHello()方法依然隱藏了父類的實現,采用了子類的實現,但是對於用new關鍵字修飾的sayBey()方法,卻執行了父類的sayBey()方法。實際上,new關鍵字的作用是隱藏父類的方法,而並不是覆蓋父類的方法。兩者的不同就在於覆蓋是完全覆蓋父類的方法,任何時候通過子類對象都無法獲取父類的方法;而隱藏則是在某種情況下是可以通過子類的對象去執行父類的方法的,這種情況就是采用了父類的變量引用了子類的具體對象。同樣的,第三個實例和第二個實例相似,都是父類的變量引用了子類的對象,所以執行的都是父類的方法。
java中的方法覆蓋
在java中,方法的覆蓋要簡單的多。子類和父類的同名方法不需要添加任何多余的關鍵字修飾,只要子類的方法與父類的方法簽名相同,子類就會自動覆蓋父類的方法。也就是說,java中默認實現了virtual和override關鍵字。而對於c#中的new關鍵字,java卻沒有相應的機制實現。這和c#正好是相反的,c#中如果子類的方法和父類的方法簽名相同,則會隱藏父類的方法;java中如果子類的方法和父類的方法簽名相同,則會覆蓋父類的方法。請看下面的代碼:
1 package study00.override; 2 3 class Person { 4 5 private String name; 6 7 public void sayHello() { 8 System.out.println("Hello, i am " + this.getName()); 9 } 10 11 public String getName() { 12 return name; 13 } 14 15 public void setName(String name) { 16 this.name = name; 17 } 18 } 19 20 class Student extends Person { 21 public void sayHello() { 22 System.out.println("Hello, i am a student, i am " + this.getName()); 23 } 24 } 25 26 public class Program { 27 public static void main(String[] args) { 28 Person p = new Student(); 29 p.setName("XiaoMing"); 30 p.sayHello(); 31 } 32 33 /** 34 * Output: 35 * Hello, i am a student, i am XiaoMing 36 */ 37 }
總結
- 多態是面向對象編程中最為重要的概念之一,而覆蓋又是體現多態最重要的方面。
- c#中可以采用virtual和override關鍵字實現方法的覆蓋,無法通過子類的對象獲得父類的同名方法調用。override修飾的方法只能是父類中被abstract、virtual或者override關鍵字修飾的方法的同名方法。
- c#中可以采用new關鍵字修飾來實現隱藏父類的方法,在引用變量和實際對象的類型一致時與override關鍵字的效果相同,在引用變量是實際對象的父類會執行父類的方法,此時父類的方法不會被覆蓋。
- java中默認實現覆蓋,但是沒有與c#中new關鍵字效果相同的機制。