Java面向对象编程 (多态)csdn连载

http://book.csdn.net/ 2006-6-30 10:14:00

6.6 多态

在第1章的1.3.9节(多态、动态绑定)已经解释了多态的实质,它是指当系统A访问系统B的服务时,系统B可以通过多种实现方式来提供服务,而这一切对系统A是透明的。比如动物园的饲养员能够给各种各样的动物喂食。图6-3显示了饲养员Feeder、食物Food和动物Animal及它的子类的类框图。

Java面向对象编程 (多态)csdn连载 - bohu83 - bohu83的博客

图6-3 饲养员Feeder、食物Food和动物Animal及它的子类的类框图

可以把Feeder、Animal和Food都看成独立的子系统。Feeder类的定义如下:

public class Feeder{

public void feed(Animal animal,Food food){

animal.eat(food);

}

}

以下程序演示了一个饲养员分别给一只狗喂肉骨头,给一只猫喂鱼。

Feeder feeder=new Feeder();

Animal animal=new Dog();

Food food=new Bone();

feeder.feed(animal,food); //给狗喂肉骨头

animal=new Cat();

food=new Fish();

feeder.feed(animal,food); //给猫喂鱼

以上animal变量被定义为Animal类型,但实际上有可能引用Dog或Cat的实例。在Feeder类的feed()方法中调用animal.eat()方法,Java虚拟机会执行animal变量所引用的实例的eat()方法。可见animal变量有多种状态,一会儿变成猫,一会儿变成狗,这是多态的字面含义。

Java语言允许某个类型的引用变量引用子类的实例,而且可以对这个引用变量进行类型转换。

Animal animal=new Dog();

Dog dog=(Dog)animal; //向下转型,把Animal类型转换为Dog类型

Creature creature=animal; //向上转型,把Animal类型转换为Creature类型

Java面向对象编程 (多态)csdn连载 - bohu83 - bohu83的博客

图6-4 类型转换

如图6-4所示,如果把引用变量转换为子类类型,则称为向下转型,如果把引用变量转换为父类类型,则称为向上转型。在进行引用变量的类型转换时,会受到各种限制。而且在通过引用变量访问它所引用的实例的静态属性、静态方法、实例属性、实例方法,以及从父类中继承的方法和属性时,Java虚拟机会采用不同的绑定机制。

下面通过具体的例子来演示多态的各种特性。在例程6-4中,父类Base和子类Sub中都定义了实例变量var、实例方法method()、静态变量staticVar和静态方法staticMethod(),此外,在Sub类中还定义了实例变量subVar和subMethod()。

例程6-4 Sub.java

package poly;

class Base{

String var="BaseVar"; //实例变量

static String staticVar="StaticBaseVar"; //静态变量

void method(){ //实例方法

System.out.println("Base method");

}

static void staticMethod(){ //静态方法

System.out.println("Static Base method");

}

}

public class Sub extends Base{

String var="SubVar"; //实例变量

static String staticVar="StaticSubVar"; //静态变量

void method(){ //覆盖父类的method()方法

System.out.println("Sub method");

}

static void staticMethod(){ //隐藏父类的staticMethod()方法

System.out.println("Static Sub method");

}

String subVar="Var only belonging to Sub";

void subMethod(){

System.out.println("Method only belonging to Sub");

}

public static void main(String args[]){

Base who=new Sub(); //who被声明为Base类型,引用Sub实例

System.out.println("who.var="+who.var); //打印Base类的var变量

System.out.println("who.staticVar="+who.staticVar); //打印Base类的staticVar变量

who.method(); //打印Sub实例的method()方法

who.staticMethod(); //打印Base类的staticMethod()方法

}

}

(1)对于一个引用类型的变量,Java编译器按照它声明的类型来处理。例如在以下代码中,编译器认为who是Base类型的引用变量,不存在subVar成员变量和subMethod()方法,所以编译出错。

Base who=new Sub(); //who是Base类型

who.subVar="123"; //编译出错,提示在Base类中没有subVar属性

who.subMethod(); //编译出错,提示在Base类中没有subMethod()方法

如果要访问Sub类的成员,必须通过强制类型的转换。

Base who=new Sub(); //who是Base类型

((Sub)who).subVar="123"; //编译成功,把Base引用类型强制转换为Sub引用类型

((Sub)who).subMethod(); //编译成功,把Base引用类型强制转换为Sub引用类型

Java编译器允许在具有直接或间接继承关系的类之间进行类型转换,对于向上转型,不必使用强制类型转换,因为子类的对象肯定也可看做父类的对象。例如一个Dog对象是一个Animal对象,也是一个Creature对象,也是一个Object对象。

Dog dog=new Dog();

Creature creature=dog; //编译成功,把Dog引用类型直接转换为Creature引用类型

Object object=dog; //编译成功,把Dog引用类型直接转换为Object引用类型

对于向下转型,必须进行强制类型转换。

Creature creature=new Cat();

Animal animal=(Animal)creature; //编译成功,把Creature引用类型强制转换为Animal引用类型

Cat cat=(Cat)creature; //编译成功,把Creature引用类型强制转换为Cat引用类型

Dog dog=(Dog)creature; //编译成功,把Creature引用类型强制转换为Dog引用类型

假如两种类型之间没有继承关系,即不在继承树的同一个继承分支上,那么Java编译器不允许进行类型转换。例如:

Dog dog=new Dog();

Cat cat=(Cat)dog; //编译出错,不允许把Dog引用类型转换为Cat引用类型

(2)对于一个引用类型的变量,运行时Java虚拟机按照它实际引用的对象来处理。例如以下代码虽然编译可以通过,但运行时会抛出ClassCastException运行时异常。

Base who=new Base(); //who引用Base类的实例

Sub s=(Sub)who; //运行时抛出ClassCastException

在运行时,子类的对象可以转换为父类类型,而父类的对象实际上无法转换为子类类型。因为通俗地讲,父类拥有的成员子类肯定也有,而子类拥有的成员父类不一定有。假定Java虚拟机能够把子类对象转换为父类类型,那么以下代码中的sub.subMethod()方法无法执行。

Base who=new Base(); //who引用Base类的实例

Sub sub=(Sub)who; //假定运行时未出错

sub.subMethod(); //sub引用变量实际上引用Base实例,而Base实例没有subMethod()方法

sub引用变量实际上引用的是Base类的实例,而Base实例没有subMethod()方法。由此可见,在运行时,Java虚拟机无法把子类对象转换为父类类型。以下代码尽管能够编译成功,但在运行时,creature变量引用的Cat对象无法转换为Dog类型,因此会抛出ClassCastException。

Creature creature=new Cat();

Animal animal=(Animal)creature; //运行正常,Cat对象可转换为Animal类型

Cat cat=(Cat)creature; //运行正常,Cat对象可以被Cat类型的引用变量引用

Dog dog=(Dog)creature; //运行时抛出ClassCastException,Cat对象不可转换为Dog类型

(3)在运行时环境中,通过引用类型变量来访问所引用对象的方法和属性时,Java虚拟机采用以下绑定规则。

实例方法与引用变量实际引用的对象的方法绑定,这种绑定属于动态绑定,因为是在运行时由Java虚拟机动态决定的。

静态方法与引用变量所声明的类型的方法绑定,这种绑定属于静态绑定,因为实际上是在编译阶段就已经做了绑定。

成员变量(包括静态变量和实例变量)与引用变量所声明的类型的成员变量绑定,这种绑定属于静态绑定,因为实际上是在编译阶段就已经做了绑定。

例如,对于以下这段代码:

Base who=new Sub(); //who被声明为Base类型,引用Sub实例

System.out.println("who.var="+who.var); //打印Base类的var变量

System.out.println("who.staticVar="+who.staticVar); //打印Base类的staticVar变量

who.method(); //打印Sub实例的method()方法

who.staticMethod(); //打印Base类的staticMethod()方法

运行时将会输出如下结果:

who.var=BaseVar

who.staticVar=StaticBaseVar

Sub method

Static Base method

再看一个例子:

public abstract class A{

abstract void method();

void test(){

method(); //到底调用哪个类的mehtod()方法?

}

}

public class B extends A{

void method(){ //覆盖父类的method()方法

System.out.println("Sub");

}

public static void main(String args[]){

new B().test();

}

}

运行类B的main()方法将打印“Sub”。方法test()在父类A中定义,它调用了方法method()。虽然方法method()在类A中被定义成抽象的,它仍然可以被调用,因为在运行时环境中,Java虚拟机会执行类B的实例的method()方法。一个实例所属的类肯定实现了父类中所有的抽象方法(否则这个类不能被实例化)。

再看一个例子:

public class A{

void method(){System.out.println("Base");}

void test(){method();}

}

public class B extends A{

void method(){System.out.println("Sub");}

public static void main(String args[]){

new A().test(); //调用类A的method()方法

new B().test(); //调用类B的method()方法

}

}

运行这段代码将打印:

Base

Sub

test()方法在父类A中定义,它调用了method()方法,和上面一个例子的区别是父类A的method()方法不是抽象的。但是通过new B().test()调用method()方法,执行的仍然是子类B的method()方法。由此可以更深入地体会动态绑定的思想:在运行时环境中,当通过B类的实例去调用一系列的实例方法(包括一个方法调用的另一个方法),将优先和B类本身包含的实例方法动态绑定,如果B类没有定义这个实例方法,才会与从父类A中继承来的实例方法动态绑定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值