多态性之编译期多态和运行期多态(JAVA版)

来源:互联网 发布:msp430单片机自学笔记 编辑:程序博客网 时间:2024/06/06 02:30

多态性之编译期多态和运行期多态(JAVA版)


    上一篇讲述了C++中的多态性,由于多态性是面向对象编程中常用的特性,所以JAVA作为面向对象的主流语言自然也有多态性,它的多态性其实和C++的概念差不多,只是实现形式和表现形式不一样。在C++中可能还会提到多态的划分,但是在JAVA中可能很多人都不会听到编译期多态和运行期多态这种划分,一般我们说到多态都是指运行期多态,因为这才是面向对象思想的真正体现之处,即OOP(面向对象)的多态性的体现,所以JAVA中我们不再讨论编译期多态这个问题,只重点讨论运行期多态,下面简称运行期多态为JAVA中的多态性。

    1. 编译期多态(静态多态)


    如上所述,此处不再多说,大家如果学习JAVA的话就不要深究这个问题了,直接进入运行期多态。

    2. 运行期多态(动态多态)


    运行期多态主要是指在程序运行的时候,动态绑定所调用的函数,动态地找到了调用函数的入口地址,从而确定到底调用哪个函数。

(1)前提

    A. 要有继承关系。
    B. 要有方法重写。其实没有也是可以的,但是如果没有这个就没有意义。
    C. 要有父类引用指向子类对象。

(2)多态中的成员访问特点

    A. 成员变量:编译看左边,运行看左边。
    B. 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
    C. 成员方法:编译看左边,运行看右边。由于成员方法存在方法重写,所以它运行看右边。
    D. 静态方法:编译看左边,运行看左边。静态和类相关,算不上重写,所以,访问还是左边的。

(3)多态性的例子

    下面的例子大部分引用自传智播客风清扬的程序,这里主要是引导大家进行理解,所以选择这些十分经典的例子进行讲解,也有部分是我新增的例子,为了帮助大家加深理解。

// 例1:多态性class Fu {public int num = 100;public void show() {System.out.println("show Fu");}public static void function() {System.out.println("function Fu");}}class Zi extends Fu {public int num = 1000;public int num2 = 200;public void show() {System.out.println("show Zi");}public void method() {System.out.println("method zi");}public static void function() {System.out.println("function Zi");}}class DuoTaiDemo {public static void main(String[] args) {//要有父类引用指向子类对象。//父 f =  new 子();Fu f = new Zi();System.out.println(f.num);//找不到符号//System.out.println(f.num2);f.show();//找不到符号//f.method();f.function();}}
运行结果:100show Zifunction Fu

    
    例1是一个经典的多态性例子,父类中和子类中有一些同样名称的成员方法和成员变量,那么这里的访问原则就遵从(2)中的原则,从运行结果可以很清楚地看到这一点。我们这里关键来说说多态性到底体现在哪里。从main方法中可以看到,我们定义了一个父类的引用然后指向了子类的对象,我们关键来研究show()方法的调用,因为这才是多态性的关键之处。这里其实存在一个向上转型,即将子类对象向上转型到了一个父类引用,然后我们利用这个父类引用调用show()方法的时候,由于在子类中重写了show()方法,并且该方法是一个普通的成员方法,并不是什么静态方法,所以,遵从“编译看左边,运行看右边”的原则,这个原则正是多态性的体现。这是因为在运行期间才实现的动态绑定,将父类的引用绑定到了子类的对象上,从而用父类的引用去调用父类和子类都有的方法时,实际上调用的是子类的方法,这就是多态性。虽然是父类的引用,但是在运行期间却绑定到了子类对象上。

    同时,大家需要注意,main方法中注释掉的部分,父类的引用即使绑定到了子类对象上,但是依然是不能访问子类的特有成员和特有方法的,比如main方法中的f.num2和f.method()都会出错,这也是多态性的一个缺点,无法调用子类特有的功能。

    上图是针对例1给出的一个内存解释,该图中的程序和例1并不完全一致,但是结构是类似的,大家根据此图可以更加清晰地看出多态的实现过程。

    下面给出一些其它例子。供大家学习参考。

// 例2:向上向下转型class 孔子爹 {public int age = 40;public void teach() {System.out.println("讲解JavaSE");}}class 孔子 extends 孔子爹 {public int age = 20;public void teach() {System.out.println("讲解论语");}public void playGame() {System.out.println("英雄联盟");}}//Java培训特别火,很多人来请孔子爹去讲课,这一天孔子爹被请走了//但是还有人来请,就剩孔子在家,价格还挺高。孔子一想,我是不是可以考虑去呢?//然后就穿上爹的衣服,带上爹的眼睛,粘上爹的胡子。就开始装爹//向上转型孔子爹 k爹 = new 孔子();//到人家那里去了System.out.println(k爹.age); //40k爹.teach(); //讲解论语//k爹.playGame(); //这是儿子才能做的//讲完了,下班回家了//脱下爹的装备,换上自己的装备//向下转型孔子 k = (孔子) k爹; System.out.println(k.age); //20k.teach(); //讲解论语k.playGame(); //英雄联盟

    例2很形象地说明了向上转型和向下转型的问题,这里的向上转型就是多态性的一个体现。注意,这个例子只是用来让大家理解,并不能直接运行,大家可以修改成合理的字母表示,然后运行。这个例子是摘自风清扬的一个经典的例子,十分形象合理。

/*例3多态的好处:A:提高了代码的维护性(继承保证)B:提高了代码的扩展性(由多态保证)猫狗案例代码*/class Animal {public void eat(){System.out.println("eat");}public void sleep(){System.out.println("sleep");}}class Dog extends Animal {public void eat(){System.out.println("狗吃肉");}public void sleep(){System.out.println("狗站着睡觉");}}class Cat extends Animal {public void eat() {System.out.println("猫吃鱼");}public void sleep() {System.out.println("猫趴着睡觉");}}class Pig extends Animal {public void eat() {System.out.println("猪吃白菜");}public void sleep() {System.out.println("猪侧着睡");}}//针对动物操作的工具类class AnimalTool {private AnimalTool(){}/*//调用猫的功能public static void useCat(Cat c) {c.eat();c.sleep();}//调用狗的功能public static void useDog(Dog d) {d.eat();d.sleep();}//调用猪的功能public static void usePig(Pig p) {p.eat();p.sleep();}*/public static void useAnimal(Animal a) {a.eat();a.sleep();}}class DuoTaiDemo2 {public static void main(String[] args) {//我喜欢猫,就养了一只Cat c = new Cat();c.eat();c.sleep();//我很喜欢猫,所以,又养了一只Cat c2 = new Cat();c2.eat();c2.sleep();//我特别喜欢猫,又养了一只Cat c3 = new Cat();c3.eat();c3.sleep();//...System.out.println("--------------");//问题来了,我养了很多只猫,每次创建对象是可以接受的//但是呢?调用方法,你不觉得很相似吗?仅仅是对象名不一样。//我们准备用方法改进//调用方式改进版本//useCat(c);//useCat(c2);//useCat(c3);//AnimalTool.useCat(c);//AnimalTool.useCat(c2);//AnimalTool.useCat(c3);AnimalTool.useAnimal(c);AnimalTool.useAnimal(c2);AnimalTool.useAnimal(c3);System.out.println("--------------");//我喜欢狗Dog d = new Dog();Dog d2 = new Dog();Dog d3 = new Dog();//AnimalTool.useDog(d);//AnimalTool.useDog(d2);//AnimalTool.useDog(d3);AnimalTool.useAnimal(d);AnimalTool.useAnimal(d2);AnimalTool.useAnimal(d3);System.out.println("--------------");//我喜欢宠物猪//定义一个猪类,它要继承自动物,提供两个方法,并且还得在工具类中添加该类方法调用Pig p = new Pig();Pig p2 = new Pig();Pig p3 = new Pig();//AnimalTool.usePig(p);//AnimalTool.usePig(p2);//AnimalTool.usePig(p3);AnimalTool.useAnimal(p);AnimalTool.useAnimal(p2);AnimalTool.useAnimal(p3);System.out.println("--------------");//我喜欢宠物狼,老虎,豹子...//定义对应的类,继承自动物,提供对应的方法重写,并在工具类添加方法调用//前面几个必须写,我是没有意见的//但是,工具类每次都改,麻烦不//我就想,你能不能不改了//太简单:把所有的动物都写上。问题是名字是什么呢?到底哪些需要被加入呢?//改用另一种解决方案。}/*//调用猫的功能public static void useCat(Cat c) {c.eat();c.sleep();}//调用狗的功能public static void useDog(Dog d) {d.eat();d.sleep();}*/}

    例3是一个更加全面的例子,也是摘自风清扬的例子,供大家学习参考。这个例子是说明一个简化的设计思想,应用到了多态,是一个稍微复杂一点的例子,大家可以用心去按照注释的思路自己思考下。

    最后,给出一个百度百科的例子,这个例子主要说明了接口和多态性的应用。

public interface Parent//父类接口{    public void simpleCall();}public class Child_A implements Parent{    public void simpleCall();    {    //具体的实现细节;    }} public class Child_B implements Parent{    public void simpleCall();    {    //具体的实现细节;    }}


    然后,我们可以看到
    Parent pa = new Child_A();
    pa.simpleCall()则显然是调用Child_A的方法;
    Parent pa = new Child_B();
    pa.simpleCall()则是在调用Child_B的方法。所以,我们对于抽象的父类或者接口给出了我们的具体实现后,pa 可以完全不用管实现的细节,只访问我们定义的方法,就可以了。事实上,这就是多态所起的作用,可以实现控制反转这在大量的J2EE轻量级框架中被用到,比如Spring的依赖注入机制。


    3. 总结

    
    总之,在JAVA中大家就不要去过多纠结编译期多态和运行期多态,只要掌握好常用的多态性即运行期多态即可。这篇文章可能存在很多纰漏,希望大家看到后给予指正,谢谢。
3 0
原创粉丝点击