多态的概念:
简单来说就是事物在运行过程中存在的不同状态,即父类或接口定义的引用变量指向子类或具体实现类的实例对象。程序调用方法在运行期才进行动态绑定,而不是引用变量的类型中定义的方法。
多态存在的前提:
1、存在继承关系,子类继承父类;
2、子类重写父类的方法;
3、父类引用指向子类对象。
具体实例:
1、定义一个父类:Animal
package demo; class Animal{
int num = 10;
static int age = 20;
public void eat() {
System.out.println("动物吃饭");
}
public static void sleep() {
System.out.println("动物睡觉");
}
public void run() {
System.out.println("动物奔跑");
}
}
2、子类:Cat继承Animal
package demo; public class Cat extends Animal{
int num=80;
static int age=90;
String name="TomCat";
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.println("猫吃饭");
}
public static void sleep(){
System.out.println("猫在睡觉");
}
public void catchMouse() {
// TODO Auto-generated method stub
System.out.println("猫抓老鼠");
}
}
3、测试类:Test1
package demo; public class Test1 { public static void main(String[] args) {
Animal am=new Cat();
am.eat();
am.sleep();
am.run();
System.out.println(am.num);
System.out.println(am.age);
//以下两行注释内容稍后解释
//am.catchMouse();
//System.out.println(am.name);
} }
以上三段代码充分体现了多态存在的前提条件:
1、存在继承关系:Cat 类继承了Animal类;
2、子类要重写父类方法:子类Cat重写(override)了父类Animal的两个方法eat(),sleep(),其中eat()为普通方法,sleep()为静态方法(static);
3、父类引用指向子类对象:测试类中Animal am=new Cat();,语句在堆内开辟了一块内存分配给子类(Cat),并把栈内存中的父类(Animal)的引用指向了这个Cat对象。
测试类运行后的结果:
可以看出:
1、子类Cat重写的父类Animal的普通方法eat()的输出结果为“猫吃饭”;
2、子类Cat重写的父类Animal的静态方法sleep()的输出结果为“动物睡觉”;
3、未被子类Cat重写的父类Animal的普通方法run()的输出结果为“动物奔跑”;
4、子类Cat继承的父类Animal属性,输出结果分别为父类属性;
5、输出子类Cat特有属性和方法则会报错。
因此,根据以上分析可以总结出多态成员访问的特点:
Animal am=new Cat();
成员变量:
编译看左(父类),运行看左(父类);
成员方法:
编译看左(父类),运行看右(子类),输入动态绑定;
静态方法:
编译看左(父类),运行看左(父类),静态方法被提升到类级别,算不上重写,所以访问还是看父类;
多态以后不能使用子类特有的属性和方法,在子类Cat中有一个特有的属性String name="TomCat";并且还有一个特有的抓老鼠的方法catchMouse()。但是在测试类Test1中,尝试调用子类的特有方法和打印子类特有的属性时,就会报错。
那么如果想要使用子类特有的属性和方法该怎么办呢?可以把这个父类引用指向的子类对象强制转换为子类Cat类型,这样am就是子类Cat类型的引用,指向的也是Cat对象了,这样就能够使用子类的一些属性和方法了。
package demo; public class Test1 { public static void main(String[] args) {
Animal am=new Cat();
am.eat();
am.sleep();
am.run();
System.out.println(am.num);
System.out.println(am.age);
//以下两行注释内容稍后解释
//am.catchMouse();
//System.out.println(am.name);
System.out.println("-------------------------");
Cat ct=(Cat)am;
ct.eat();
ct.sleep();
ct.run();
ct.catchMouse();
System.out.println(ct.num);
System.out.println(ct.age);
System.out.println(ct.name);
} }
执行强转语句Cat ct=(Cat)am;后,ct就指向了最开始在堆内存中创建的那个Cat对象了。这就是多态的功能,使用起来十分的灵活,觉少了多余对象的创建,不用为了使用子类的某个方法再去堆内存中开辟一块新的空间给一个新的子类对象了。
花木兰替父从军的例子:
大家都知道花木兰替父从军的例子,花木兰替父亲花弧从军。那么这时候花木兰是子类,花弧是父类。花弧有自己的成员属性年龄,姓名,性别。花木兰也有这些属性,但是很明显二者的属性值完全不一样。花弧有自己的非静态成员方法‘骑马杀敌’,同样花木兰也遗传了父亲一样的方法‘骑马杀敌’。花弧还有一个静态方法‘自我介绍’,每个人都可以问花弧姓甚名谁。同时花木兰还有一个自己特有的非静态成员方法‘涂脂抹粉’。但是,现在花木兰替父从军,女扮男装。这时候相当于父类的引用(花弧这个名字)指向了子类对象(花木兰这个人),那么在其他类(其他的人)中访问子类对象(花木兰这个人)的成员属性(姓名,年龄,性别)时,其实看到的都是花木兰她父亲的名字(花弧)、年龄(60岁)、性别(男)。当访问子类对象(花木兰这个人)的非静态成员方法(骑马打仗)时,其实都是看到花木兰自己运用十八般武艺在骑马打仗。当访问花木兰的静态方法时(自我介绍),花木兰都是用她父亲的名字信息在向别人作自我介绍。并且这时候花木兰不能使用自己特有的成员方法‘涂脂抹粉’。-----多态中的向上转型
那么终于一将功成万骨枯,打仗旗开得胜了,花木兰告别了战争生活。有一天,遇到了自己心爱的男人,这时候爱情的力量将父类对象的引用(花弧这个名字)强制转换为子类对象本来的引用(花木兰这个名字),那么花木兰又从新成为了她自己,这时候她完全是她自己了。名字是花木兰,年龄是28,性别是女,打仗依然那样生猛女汉子,自我介绍则堂堂正正地告诉别人我叫花木兰。终于可以使用自己特有的成员方法‘涂脂抹粉’了。从此,花木兰完全回到了替父从军前的那个花木兰了。-----多态中的向下转型
向上转型向下转型一定是在多态这个前提下,同时向上转型是安全的,向下转型则不安全。比如强制将女儿变成父亲,则女儿可以使用父亲的身份存在,反之,将父亲变成女儿,就变成东方不败了,系统此时就会报错非法类型转换。另外开发中一般利用多态声明形式参数,并将创建子类的匿名对象作为实际参数。