多态性之编译期多态和运行期多态(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();
- }
- }
- 运行结果:
- 100
- show Zi
- function 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); //40
- k爹.teach(); //讲解论语
- //k爹.playGame(); //这是儿子才能做的
- //讲完了,下班回家了
- //脱下爹的装备,换上自己的装备
- //向下转型
- 孔子 k = (孔子) k爹;
- System.out.println(k.age); //20
- k.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中大家就不要去过多纠结编译期多态和运行期多态,只要掌握好常用的多态性即运行期多态即可。这篇文章可能存在很多纰漏,希望大家看到后给予指正,谢谢。
推荐阅读
- java IO流相关的类的分类和总结
- java 数组声明定义 数组内存分配 数组初始化 数组引用 数组的遍历
- Java中构造器内部的多态方法的行为
- Java向下转型的意义与塑型的三个方面
- Java自学Math类——自学笔记
- Java自学笔记|接口之间的继承
- Java自学之反射——自学笔记
- Java自学笔记|System.in与System.out(标准输入与输出)详解
- Java自学之break与标号一同使用——自学笔记