多態是面向對象軟件的基本原理之一。該術語一般表示能夠具備多種形式的事物。在面向對象的方法中,多態使編寫具備後期綁定引用的程序成爲可能。儘管在Java中建立多態引用很容易,但其背後的概念對總體編程產生了更深遠的影響。本文結合在優銳課學習到的知識點,探討了有關多態性及其對面向對象編程的影響的一些複雜細節。java
多態引用是一個變量,能夠在不一樣的時間點引用不一樣類型的對象。它一般與它所引用的類兼容。 例如,在如下狀況下:編程
Employee employee;
'employee'是一個引用變量,能夠引用Employee類的實例。參考變量對參考對象的限定取決於其兼容性。這彷佛是惟一可靠的條件,但事實並不是如此,特別是在實現多態時。規則太嚴格了,可是經過結合「具備多種形式」的思想,多態性使得靈活性更高。這意味着多態引用保證了它能夠在不一樣的時間點引用不一樣類型的對象,而不是爲兼容性而徹底依賴。所以,若是能夠在一個時間點使用引用來調用方法,則能夠將其動態更改成指向另外一個對象,並在下一次調用其餘方法。經過提供引用變量的另外一個使用維度,這能夠利用靈活性。數組
當引用變量綁定到沒法在運行時更改的對象時,或者換句話說,方法調用與方法定義的綁定是在編譯時完成的,稱爲靜態綁定。若是綁定在運行時是可更改的,例如在多態引用的狀況下,綁定的決策僅在執行期間作出,則稱爲動態綁定或後期綁定。二者在面向對象程序設計中都有其用途,並不是一個都賽過另外一個。可是,在多態引用的狀況下,推遲的綁定承諾在靈活性方面使其比編譯時綁定更具優點,但另外一方面,這會下降性能開銷。可是,這在很大程度上是能夠接受的,而且在提升效率方面,開銷一般具備很小的吸引力。.架構
在Java中,能夠經過兩種方式建立多態引用:使用繼承或使用接口。ide
引用變量引用類的實例。對於繼承層次結構,若是引用變量在層次結構樹中聲明爲父類類型,則引用對象能夠指向層次結構中任何類的實例。這意味着,在Java中,由於Objectclass是全部類的父類或超類,或者換句話說,Java中的全部類其實是Object類的子類隱式或顯式地是對象類的引用變量。對象類型能夠引用Java中的任何類實例。這就是咱們的意思。性能
1 Employee employee; 2 Object object; 3 employee = new Employee(); 4 object = employee; // This is a valid assignment
若是狀況相反,則以下所示:學習
1 Employee employee; 2 Object object = new Object(); 3 employee = (Employee)object // Valid, but needs explicit cast
觀察到它須要顯式強制轉換;只有這樣,它才能成爲有效的聲明。能夠看出,這種反向分配對於在許多狀況下出現問題的邊緣來講沒有多大用處。這是由於Object實例的功能與Employee引用變量預期的功能幾乎沒有關係。關係is-a能夠從employee-is-an-object派生派生;在這種狀況下,相反的關係(例如,對象是僱員)太牽強。this
讓咱們嘗試藉助示例來理解它。url
圖1:從驅動程序類,公司派生的類spa
稱爲Company的驅動程序類建立一個僱員列表,並調用paySalary()方法。薪資類維護公司中不一樣類型員工的列表。請注意,該數組被聲明爲派生自Employee類(全部僱員子類的父級或超類)的引用變量的數組。結果,能夠用從Employee類的任何子類(例如CommissionEmployee, HourlyEmployee, SalariedEmployee)建立的對象引用填充數組。在paySalary()定義中,根據數組中的對象引用調用適當的salary()方法。所以,對salary()方法的調用是多態的,很明顯,每一個類都有其本身版本的salary()方法。
Payroll類中的employee數組不表明特定類型的Employee。它用做能夠指向任何類型的Employee子類引用的句柄。儘管繼承的類共享做爲後代繼承的一些公共數據,可是它們具備各自的屬性集。
這是該示例在Java中的快速實現。
1 package org.mano.example; 2 public class Company 3 { 4 public static void main( String[] args ) 5 { 6 Payroll payroll = new Payroll(); 7 payroll.paySalary(); 8 } 9 } 10 package org.mano.example; 11 import java.util.ArrayList; 12 import java.util.List; 13 public class Payroll { 14 private List<Employee> employees = 15 new ArrayList<>(); 16 public Payroll() { 17 employees.add(new 18 SalariedEmployee("Harry Potter", 19 "123-234-345",7800)); 20 employees.add(new 21 CommissionEmployee("Peter Parker", 22 "234-345-456",2345.67,0.15)); 23 employees.add(new 24 HourlyEmployee("Joker Poker", 25 "456-567-678",562.36,239.88)); 26 } 27 public void paySalary() { 28 for (Employee e: employees) { 29 System.out.println 30 ("----------------------------------------------------"); 31 System.out.println(e.toString()); 32 System.out.printf 33 ("Gross payment: $%,.2f\n",e.salary()); 34 System.out.println 35 ("----------------------------------------------------"); 36 } 37 } 38 } 39 40 package org.mano.example; 41 42 public abstract class Employee { 43 protected String name; 44 protected String ssn; 45 public Employee(String name, String ssn) { 46 this.name = name; 47 this.ssn = ssn; 48 } 49 public String getName() { 50 return name; 51 } 52 public void setName(String name) { 53 this.name = name; 54 } 55 public String getSsn() { 56 return ssn; 57 } 58 public void setSsn(String ssn) { 59 this.ssn = ssn; 60 } 61 @Override 62 public String toString() { 63 return String.format("%s\nSSN: %s", 64 getName(),getSsn()); 65 } 66 public abstract double salary(); 67 } 68 69 package org.mano.example; 70 public class SalariedEmployee extends Employee { 71 protected double basicSalary; 72 public SalariedEmployee(String name, String ssn, 73 double basicSalary) { 74 super(name, ssn); 75 setBasicSalary(basicSalary); 76 } 77 public double getBasicSalary() { 78 return basicSalary; 79 } 80 public void setBasicSalary(double basicSalary) { 81 if(basicSalary>= 0.0) 82 this.basicSalary = basicSalary; 83 else 84 throw new IllegalArgumentException("basic " + 85 "salary must be greater than 0.0"); 86 } 87 @Override 88 public double salary() { 89 eturn getBasicSalary(); 90 } 91 @Override 92 public String toString() { 93 return String.format("%s\nBasic Salary: $%,.2f", 94 super.toString(),getBasicSalary()); 95 } 96 } 97 98 package org.mano.example; 99 public class HourlyEmployee extends Employee { 100 protected double wage; 101 protected double hours; 102 public HourlyEmployee(String name, String ssn, 103 double wage, double hours) { 104 super (name, ssn); 105 setWage(wage); 106 setHours(hours); 107 } 108 public double getWage() { 109 return wage; 110 } 111 public void setWage(double wage) { 112 if(wage >= 0.0) 113 this.wage = wage; 114 else 115 throw new IllegalArgumentException("wage " + 116 "must be > 0.0"); 117 } 118 public double getHours() { 119 return hours; 120 } 121 public void setHours(double hours) { 122 if(hours >= 0.0) 123 this.hours = hours; 124 else 125 throw new IllegalArgumentException("hours " + 126 "must be > 0.0"); 127 } 128 @Override 129 public double salary() { 130 return getHours() * getWage(); 131 } 132 @Override 133 public String toString() { 134 return String.format("%s\nWage: $%, 135 .2f\nHours worked: %,.2f", 136 super.toString(),getWage(),getHours()); 137 } 138 } 139 140 package org.mano.example; 141 public class CommissionEmployee extends Employee { 142 protected double sales; 143 protected double commission; 144 public CommissionEmployee(String name, String ssn, 145 double sales, double commission) { 146 super(name, ssn); 147 setSales(sales); 148 setCommission(commission); 149 } 150 public double getSales() { 151 return sales; 152 } 153 public void setSales(double sales) { 154 if(sales >=0.0) 155 this.sales = sales; 156 else 157 throw new IllegalArgumentException("Sales " + 158 "must be >= 0.0"); 159 } 160 public double getCommission() { 161 return commission; 162 } 163 public void setCommission(double commission) { 164 if(commission > 0.0 && commission < 1.0) 165 this.commission = commission; 166 else 167 throw new IllegalArgumentException("Commission " + 168 "must be between 0.0 and 1.0"); 169 } 170 @Override 171 public double salary() { 172 return getCommission() * getSales(); 173 } 174 @Override 175 public String toString() { 176 return String.format("%s\nSales: %, 177 .2f\nCommission: %,.2f", 178 super.toString(),getSales(),getCommission()); 179 } 180 }
接口的多態性與前面的示例很是類似,不一樣之處在於,這裏的多態性規則是根據Java接口指定的規範進行的。接口名稱能夠用做引用變量,就像咱們對上面的類名稱所作的那樣。它引用實現該接口的任何類的任何對象。這是一個例子。
1 package org.mano.example; 2 public interface Player { 3 public enum STATUS{PLAY,PAUSE,STOP}; 4 public void play(); 5 public void stop(); 6 public void pause(); 7 } 8 9 package org.mano.example; 10 public class VideoPlayer implements Player { 11 private STATUS currentStatus = STATUS.STOP; 12 @Override 13 public void play() { 14 if(currentStatus == STATUS.STOP || 15 currentStatus == STATUS.PAUSE) { 16 currentStatus = STATUS.PLAY; 17 System.out.println("Playing Video..."); 18 } 19 else 20 System.out.println("I am ON playing man!"); 21 } 22 @Override 23 public voidstop() { 24 if(currentStatus == STATUS.PLAY || 25 currentStatus == STATUS.PAUSE) { 26 currentStatus = STATUS.STOP; 27 System.out.println("Video play stopped."); 28 } 29 else 30 System.out.println("Do you want me to go fishing?"); 31 } 32 @Override 33 public void pause() { 34 if(currentStatus == STATUS.PLAY) { 35 currentStatus = STATUS.PAUSE; 36 System.out.println("Video play paused."); 37 } 38 else 39 System.out.println("I'm a statue. You froze me 40 already!"); 41 } 42 } 43 44 package org.mano.example; 45 public class AudioPlayer implements Player { 46 private STATUS currentStatus = STATUS.STOP; 47 @Override 48 public void play() { 49 if(currentStatus == STATUS.STOP || 50 currentStatus == STATUS.PAUSE) { 51 currentStatus = STATUS.PLAY; 52 System.out.println("Playing Audio..."); 53 } 54 else 55 System.out.println("I am ON playing man!"); 56 } 57 @Override 58 public void stop() { 59 if(currentStatus == STATUS.PLAY || 60 currentStatus == STATUS.PAUSE) { 61 currentStatus = STATUS.STOP; 62 System.out.println("Audio play stopped."); 63 } 64 else 65 System.out.println("Do you want me to go fishing?"); 66 } 67 @Override 68 public void pause() { 69 if(currentStatus == STATUS.PLAY) { 70 currentStatus = STATUS.PAUSE; 71 System.out.println("Audio play paused."); 72 } 73 else 74 System.out.println("I'm a statue. You froze me 75 already!"); 76 } 77 } 78 79 package org.mano.example; 80 public class PlayerApp { 81 public static void main(String[] args) { 82 Player player= new VideoPlayer(); 83 player.play(); 84 player.pause(); 85 player.stop(); 86 player= new AudioPlayer(); 87 player.play(); 88 player.pause(); 89 player.stop(); 90 } 91 }
請注意,在PlayerApp中,咱們已經使用接口Player來聲明對象引用變量。引用變量player能夠引用實現Player接口的任何類的任何對象。爲了證實這一點,咱們在這裏使用了同一播放器變量來引用VideoPlayer對象和AudioPlayer對象。運行時調用的方法是特定於其引用的類對象的方法。實現接口的類與接口自己之間的關係是父子關係,正如咱們在帶有繼承的多態示例中所看到的。它也是一個is-arelationship,並構成了多態性的基礎。
經過類繼承或經過接口實現多態性之間的差別是一個選擇問題。實際上,區別在於理解類和接口的屬性和特性。除了瞭解其性質外,沒有嚴格的規則來定義什麼時候使用。這超出了本文的範圍。可是,在多態中,這個想法既適合而且也有能力完成咱們想對它們進行的操做。就這樣。抽絲剝繭,細說架構那些事——【優銳課】