继承,封装,多态是面向对象程序设计中的三大特性.其实从某种程度上讲继承和封装都是在为多态做准备,下面我们来了解多态这个重要的概念及其实现原理.
一.什么是多态
所谓的多态就是指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用).
我们知道java支持向上转型,所以对于从同一个基类继承而来的子类,我们都可以把其对象当成基类对象来处理.所以多态 就是指调用基类的某些方法,依据传入的子类对象的不同而表现出不同的特性.(类似于现实中我们在不同的软件下,按下相同的快捷键会有不同的反应).下面的代码示范了多态性:Bicycle Tricycle Unicycle都是Cycle类的子类,然后这四个类中都有wheels方法用于返回其轮子的数量.RideTest类中的wheels方法将所有传入的对象都看成Cycle对象,但是当我们向里面传入Bicycle,Tricycle,Unicycle对象时是可以得到正确结果的,这就是多态.
package lkl;
public class Cycle {
public int wheels (){
return 0;
}
}
public class Tricycle extends Cycle{
public int wheels(){
return 3;
}
}
public class BiCycle extends Cycle{
public int wheels(){
return 2;
}
}
public class Unicycle extends Cycle{
public int wheels(){
return 1;
}
}
public class RideTest {
public int wheels(Cycle cy){
return cy.wheels();
}
public static void main(String[] args){
RideTest rt = new RideTest();
///根据传入的不同子类表现不同的行为,称为多态
System.out.println(rt.wheels(new Unicycle()));
System.out.println(rt.wheels(new BiCycle()));
System.out.println(rt.wheels(new Tricycle()));
二.多态的实现原理
多态实现依赖于两个原理:1.向上转型,2.后期绑定.
java提供的向上转型允许我们将子类的对象当成基类对象处理,这种转型是多态的必要条件因为多态要求我们将所有的子类对象当成基类对象处理.向上转型的合理性在于所有基类有的接口,子类也必定会有,这样转型后就不会发生调用子类不存在接口的错误.
我们知道对于一般的实例方法,都应该有一个调用它的对象,所以编译器要将一个方法的调用和一个实体(对象)联系起来(绑定).如果在程序执行前就进行绑定,那么称为前期绑定.但对于多态来说,我们只有在运行时才知道到底是那个对象调用的方法,所以前期判定是行不通的.java引入了后期绑定(也叫动态绑定),它允许我们在程序运行时根据对象的类型进行绑定(这要求能够在运行时判定对象的类型).这也是实现多态的核心所在.在C++中我们用virtual关键字表明一个函数能够被其子类重写,存在多态性.对应到java中,除了private static 修饰的函数,其余函数都默认为后期绑定的,即存在多态性的可能.
三.陷阱
首先并不是所有的函数都具有多态性,像上面说的用private 或static修饰的函数是不存在多态性的,因为多态性的前提是继承和重写,基类接口需要对子类可见.private修饰的函数不能被子类显示调用,而static函数是属于类所有的,也可以看成不能被继承的函数.所以这两类函数不具备多态性.如果我们依据多态的概念来看,似乎基类中非private的成员变量也会具有多态性,但是实际上并没有,这时java的规定.下面的代码显示了这三种情况:
package lkl;
public class BaseTest {
public int i=1;
public int getFiled(){
return i;
}
private void privateTest(){
System.out.println("BaseTest.privateTest()");
}
///static函数
public static void staticTest(){
System.out.println("BaseTest.staticTest()");
}
}
package lkl;
public class Test1 extends BaseTest{
public int i=9;
public int getFiled(){
return i;
}
///试图重写基类的privateTest()
private void privateTest(){
System.out.println("Test1.privateTest()");
}
///试图重写基类的staticTest()
///@Override 添加Override出错,说明不能重写
public static void staticTest(){
System.out.println("Test1.staticTest");
}
public static void main(String[] args){
BaseTest bt = new Test1();
///域是不存在多态的
System.out.println(bt.i);
System.out.println(bt.getFiled());
bt.f();
///staticTest的调用不会产生多态性
bt.staticTest();
}
}
四.一道习题
创建一个包含两个方法的基类.在第一个方法中可以调用第二个方法,然后产生一个继承自该基类的导出类,并且覆盖基类中的第二个方法.为该导出类创建一个对象,将它向上转型到基类类型并调用第一个方法,解释发生的情况.
public class Test1 extends BaseTest{
@Override///重写second方法
public void second(){
//super.g();
System.out.println("Test1.second()");
}
public static void main(String[] args){
BaseTest bt = new Test1();
bt.first();
}
}
public class BaseTest {
public void first(){
System.out.println("Base.first()");
second();
}
public void second(){
System.out.println("Base.second()");
}
}
结果为:
Base.first()
Test1.second()
这种情况的原因在于我们调用first这个方法的对象是子类对象,但是因为first方法没有被子类重写,所以没有表现出多态;
我们在first中调用second函数的对象也是子类对象,因为second函数在子类中重写了,所以表现出了多态性.
这个题目说明重写也是多态的必要条件.