多态性之编译期多态和运行期多态(JAVA版)
上一篇讲述了C++中的多态性,由于多态性是面向对象编程中常用的特性,所以JAVA作为面向对象的主流语言自然也有多态性,它的多态性其实和C++的概念差不多,只是实现形式和表现形式不一样。在C++中可能还会提到多态的划分,但是在JAVA中可能很多人都不会听到编译期多态和运行期多态这种划分,一般我们说到多态都是指运行期多态,因为这才是面向对象思想的真正体现之处,即OOP(面向对象)的多态性的体现,所以JAVA中我们不再讨论编译期多态这个问题,只重点讨论运行期多态,下面简称运行期多态为JAVA中的多态性。
2. 运行期多态(动态多态)
运行期多态主要是指在程序运行的时候,动态绑定所调用的函数,动态地找到了调用函数的入口地址,从而确定到底调用哪个函数。
(1)前提
A. 要有继承关系。
B. 要有方法重写。其实没有也是可以的,但是如果没有这个就没有意义。
C. 要有父类引用指向子类对象。
B. 要有方法重写。其实没有也是可以的,但是如果没有这个就没有意义。
C. 要有父类引用指向子类对象。
(2)多态中的成员访问特点
A. 成员变量:编译看左边,运行看左边。
B. 构造方法:创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化。
C. 成员方法:编译看左边,运行看右边。由于成员方法存在方法重写,所以它运行看右边。
D. 静态方法:编译看左边,运行看左边。静态和类相关,算不上重写,所以,访问还是左边的。
(3)多态性的例子
(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中大家就不要去过多纠结编译期多态和运行期多态,只要掌握好常用的多态性即运行期多态即可。这篇文章可能存在很多纰漏,希望大家看到后给予指正,谢谢。