时间在流逝,春节也快要到了。别的同学都已经在家好多天,自己还在学校继续忙着。要想人前显贵,就得人后受罪,依然记得高中的时候物理老师送给我们的话,虽然有时候感觉很寂寞,但是想想现在的付出是为了以后的美好生活,还是会很有动力。
今天总结面向对象设计中的多态(polymorphism)了,刚开始接触多态这个概念的时候觉得很混乱,随着学习的不断深入,渐渐开始理解这种机制。
一、多态的概念
从字面上理解,多态可以理解为多种形态,多种类型。我们知道,通过继承可以使用父类型(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虚拟机在运行是动态绑定的方法的实现,这是由变量的实际类型决定的。