時間在流逝,春節也快要到了。別的同學都已經在家好多天,自己還在學校繼續忙着。要想人前顯貴,就得人後受罪,依然記得高中的時候物理老師送給我們的話,雖然有時候感覺很寂寞,但是想想現在的付出是爲了以後的美好生活,還是會很有動力。
今天總結面向對象設計中的多態(polymorphism)了,剛開始接觸多態這個概念的時候覺得很混亂,隨着學習的不斷深入,漸漸開始理解這種機制。
- 多態的概念
- 多態的實現機制
- final關鍵字
一、多態的概念
從字面上理解,多態可以理解爲多種形態,多種類型。我們知道,通過繼承可以使用父類型(supertype)來引用子類型變量(subtype),也就是說,每個子類對象也可以看做是超類的對象,在程序中可以將多種類型(從同一基類導出的類型)視爲同一類型來處理。而同一段代碼也就可以毫無差別的運行在不同類型之上了。這些,就是實現多態機制的基礎。一個對象變量可以引用多種實際類型的現象被稱之爲多態。
來看一段代碼:
import java.util.*;
/**
* This program demonstrates inheritance.
* @version 1.21 2013/01/25
* @author LiMing
*/
public class ManagerTest
{
public static void main(String[] args)
{
// construct a Manager object
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
Employee[] staff = new Employee[3];
// fill the staff array with Manager and Employee objects
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
}
}
class Employee
{
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
class Manager extends Employee
{
/**
* @param n the employee's name
* @param s the salary
* @param year the hire year
* @param month the hire month
* @param day the hire day
*/
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double b)
{
bonus = b;
}
private double bonus;
}
我們可以想象,一個經理Manager必然是一個僱員Employee,經理是子類型,而僱員是父類型。在代碼中我們通過
staff[0]=boss; 這條語句將Manager型的引用賦給了Employee類型,在Employee類中擁有一個方法getSalary(),而在Manager中有同樣的一個方法getSalary(),程序運行的最後結果我們驚奇的發現,Employee型的對象引用竟然正確的調用了我們希望的Manager的getSalary方法。這個例子作爲多態的簡單演示,下面我們來討論多態的實現機制。
二、多態的實現機制
首先我們先了解幾個術語:綁定(binding),《Java編程思想》中說“將一個方法調用同一個方法主體關聯起來被稱作綁定”
。
這句話個人理解是這樣的:假設集合A是一個由所有類中的所有的方法組成的方法集合,集合B是由全部的對象以及每個對象所擁有
的動作(方法)組成的集合,當我們程序在出現方法調用的時候,必然是由集合B和集合A中的兩個元素進行交互。B中的一個元素調
用A中的方法時,通過一個過程來找到確定的該調用的方法,這個過程被稱之爲綁定。
在程序執行之前進行的綁定被稱之爲前期綁定(靜態綁定)這是由編譯器以及連接程序確定的(至於前期綁定的具體過程還請高手不
吝賜教),在程序運行過程中實現的綁定被稱之爲後期綁定(動態綁定、運行時綁定)。在Java中所有的方法都是通過動態綁定來實
現多態的
我們主要來研究動態綁定這個概念,首先我們需要區分變量的實際類型(actual type)與聲明類型(declared type),我們
知道一個變量必須要被聲明爲某種類型。請看下面這兩句代碼:
Employee obj = new Manager();
obj.getSalary();
這裏obj的聲明類型是Employee,一個引用變量可以是一個null值或者是一個對聲明類型實例的引用。實例可以是該類型本身或者它
的子類型。變量的實際類型是被變量引用的對象的實際類。在這裏obj引用的是Manager類型的變量,所以obj的聲明類型是Employee
,實際類型是Manager。當調用getSalary方法時是由obj的實際類型所決定的,因爲在程序運行之前我們並不能夠確定某個變量將引
用何種類型,所以需要在運行時加以確定,從而調用相應的方法,這就是動態綁定。
我們來看看動態綁定的工作機制:
1>編譯器查看對象的聲明類型以及方法名。假設調用obj.function(param),且隱式參數obj被聲明爲C類的對象(C類作爲子類來看
待)。需要注意的是:有可能存在多個名字爲funciton的,但參數類型不一樣的方法。例如可能存在function(int),function
(String)等。編譯器會列舉C類中的所有名爲function的方法和其超類中訪問屬性爲public的且名爲function的方法。至此編譯器
已經獲得了所有可能被調用的候選方法。
2>接下來編譯器將查看調用方法時提供的類型參數。如果所有名爲function的方法中存在一個與提供的類型參數完全匹配,就選
擇這個方法。這個過程被稱之爲重載解析(overloading resolution)
總之,當程序運行時並且採用動態綁定調用方法時,虛擬機一定調用與obj所引用的對象的實際類型最適合的那個類的方法。假設
obj的實際類型是A,需要調用function(int)型的方法,它是B類的子類,如果A類定義了function(int)方法,就直接調用它;
否則在A類的超類中尋找function(int),以此類推。
我們來看一段代碼:
public class DynamicBinding {
/**
* 演示動態綁定
* @author LiMing
* @since 2013/01/25
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Person[] p =new Person[3];
p[0]=new Person();
p[1]=new Student();
p[2]=new GraduateStudent();
for(Person obj:p)
printObject(obj);
}
public static void printObject(Object o){
System.out.println(o.toString());
}
}
class Person{
public Person(){
}
public String toString(){
return "This is class Person!";
}
}
class Student extends Person{
public Student(){
}
public String toString(){
return "This is class Student!";
}
}
class GraduateStudent extends Student{
public GraduateStudent(){
}
public String toString(){
return "This is calss GraduateStudent!";
}
}
匹配方法的簽名和綁定的方法的實現是兩個獨立的事情。引用變量的聲明類型決定了編譯時匹配哪個方法。編譯器在編譯時,會根據類型參數、參數個數和參數順序找到匹配的方法。一個方法可能在幾個子類中都被實現。Java虛擬機在運行是動態綁定的方法的實現,這是由變量的實際類型決定的。