一、什么是多态?
用一句话概括:事物在运行过程中存在不同的状态。先以教科书举例说明,下文再举一个花木兰替父从军的例子加以解释和说明,多态的存在有三个前提:
1、需要有继承关系
2、子类重写父类的方法
3、父类引用指向子对,
但是,其中又有很多细节需要注意。首先我们定义两个类,一个父类Animal,一个子类Cat
父类Animal:
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Animal { 4 int num=10; 5 static int age=20; 6 public void eat(){ 7 System.out.println("动物吃饭"); 8 } 9 public static void sleep(){ 10 System.out.println("动物在睡觉"); 11 } 12 public void run(){ 13 System.out.println("动物在奔跑"); 14 } 15 16 }
子类Cat
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Cat extends Animal { 4 int num=80; 5 static int age=90; 6 String name="tomCat"; 7 public void eat(){ 8 System.out.println("猫吃饭"); 9 } 10 public static void sleep(){ 11 System.out.println("猫在睡觉"); 12 } 13 public void catchMouse(){ 14 System.out.println("猫在抓老鼠"); 15 } 16 17 }
测试类Demo:
1 package com.wangjun.ThinkingInJava.java_14.another; 2 3 public class Demo { 4 5 public static void main(String[] args) { 6 Animal am=new Cat(); 7 am.eat(); 8 am.sleep(); 9 am.run(); 10 //am.catchMouse(); 11 System.out.println(am.num); 12 System.out.println(am.age); 13 } 14 15 }
解释:以上的三段代码充分体现了多态的三个前提,即:
1、存在继承关系:Cat类继承了Animal类。
2、子类要重写父类的方法:子类重写(override)了父类的两个成员方法eat(),sleep()。其中eat()是非静态的,sleep()是静态的(static)。
3、父类数据类型的引用指向了子类对象:Animal am=new Cat(),语句在堆内存中开辟了子类Cat的对象,并把栈内存中的父类(Animal)的引用指向了这个Cat对象。
到此,满足了Java多态的必要三个前提。
二、更深层次的研究
如果再深究一点呢,我们可以看看上面测试类的输出结果,或许对多态会有更深层次的认识。下面是执行结果:
1 猫吃饭 2 动物在睡觉 3 动物在奔跑 4 10 5 20
可以看出来,子类cat重写了父类Animal的非静态成员方法am.eat()的输出结果为:猫吃饭
子类cat重写了父类Animal的静态成员方法am.sleep()的输出结果为:动物在睡觉
未被子类Cat重写的父类Animal方法am.run()的输出结果为:动物在奔跑
System.out.println(am.num);//输出结果为10
System.out.println(am.age);//输出结果为20
基于以上情况,可以总结出多态成员访问的特点:
成员变量
编译看左边(父类),运行看左边(父类)
成员方法
编译看左边(父类),运行看右边(子类),实现动态绑定
静态方法
编译看左边(父类),运行看左边(父类)
静态和类相关,算不上重写,所以访问还是左边的,只有非静态成员的方法,编译看左边运行看右边
三、多态的缺点
多态不能使用子类特有的属性和方法。往上面的代码看,子类Cat有一个特有的属性String name=“tomCat”,并且还有一个抓老鼠的方法catchMouse()
但是在测试类中我们尝试调用子类特有的方法catchMouse()和打印子类特有的成员属性String name="tomCat",就会报错。
am.catchMouse();
System.out.println(am.name);
原因就是多态的弊端:不能使用子类特有的成员属性和子类特有的成员方法。
如果在代码执行过程中还想使用Cat类中特有的属性String name和它特有的成员方法catchMouse(),怎么办呢?那我们就可以把这个父类引用指向了子类对象的am再强制变回cat类型。这样am就是Cat类型的引用了,指向的也是Cat对象了,自然也能使用Cat类的一切属性和一切的成员方法了。
1 public class Demo_Test { 2 3 public static void main(String[] args) { 4 Animal am=new Cat(); 5 am.eat(); 6 am.sleep(); 7 am.run(); 8 System.out.println(am.num); 9 System.out.println(am.age); 10 System.out.println("-------------------"); 11 12 Cat cat=(Cat)am; 13 cat.eat(); 14 cat.sleep(); 15 cat.run(); 16 cat.catchMouse(); 17 18 }
显示结果:
1 猫吃饭 2 动物在睡觉 3 动物在奔跑 4 10 5 20 6 ------------------- 7 猫吃饭 8 猫在睡觉 9 动物在奔跑 10 猫在抓老鼠
很明显,执行强转换语句Cat cat=(Cat) am之后,cat就指向最开始在堆内存中创建的那个Cat类型的对象了。这就是多态的魅力吧,虽然它有缺点,但是它确实十分灵活,减少多余对象的创建,不是说为了使用子类的某个方法又去重新再堆内存找那个开辟一个新的子类对象。以上。。。
四、花木兰替父从军形象化多态
花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄、姓名、性别。花木兰也有这些属性,但是很明显二者是属性完全不一样。花弧有自己的非静态成员方法:骑马杀敌,同样花木兰也遗传了父亲一样的方法骑马杀敌。花弧还有一个静态方法自我介绍,每个人都可以问花弧姓甚名谁同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这个人)的非静态成员方法(骑马打仗)时,其实都是看到花木兰自己运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰自己都是用她父亲的名字信息在向别人作自我介绍。并且这时候花木兰不能使用自己特有的成员方法‘涂脂抹粉’。-----多态中的向上转型
那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了自己心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象本来的引用(花木兰这个名字),那么花木兰又从新成为了她自己,这时候她完全是她自己了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。OMG!终于,终于可以使用自己特有的成员方法‘涂脂抹粉’了。从此,花木兰完全回到了替父从军前的那个花木兰了。并且和自己心爱的男人幸福的过完了一生。-----多态中的向下转型
----------------------------------------------------------华丽的分割线---------------------------------------------------------------
大家记得哈,向上转型向下转型一定是在多态这个前提下哈,否则强制将女儿变成父亲,或者将父亲变成女人,就变成东方不败了,系统此时就会报错非法类型转换。哈哈哈哈哈。另外开发中一般是利用多态声明形式参数,并将创建子类的匿名对象作为实际参数。以上。