什么是多态?
多态,书面解释是“同一个行为具有不同表现形式或形态的能力”,我的理解是,如果一个对象可以衍生出多种形态,那么这就叫多态。对象就相当于模板。举个例子,比如制衣厂里进行生产的图纸。制衣图纸就有很多不同的表达或实现,比如有粉色衣服、黑色衣服、白色衣服、大小号衣服等等。当制衣厂根据图纸做出五颜六色尺码不同的衣服后,我们就可以说"制衣图纸"这个对象具备多态性。多态的本质就是允许将子类对象的地址调用到父类栈上的引用变量。
多态的前提
- 子类继承父类,比如
class Zi extends Fu{ }
- 子类重写父类方法,重写就是将父类中的方法照抄一遍,可以更改方法体。
- 父类引用指向子类对象,比如
Fu fu = new Zi();
,左边的Fu fu
是父类引用,左边的new Zi();
是子类对象。
多态的分类
- 重载式多态,也叫编译时多态。也就是说,这种多态再编译时已经确定好了。重载大家都知道,方法名相同而参数列表不同 的一组方法就是重载。在调用这种重载的方法时,通过传入不同的参数最后得到不同的结果。
- 重写式多态,也叫运行时多态。这种多态通过动态绑定技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个类的方法。我们接下来讲的所有多态都是重写式多态,因为它才是面向对象编程中真正的多态。
多态的优缺点
- java 多态的好处:
A:提高了代码的维护性(继承保证)
B:提高了代码的扩展性(由多态保证) - 多态的弊端
A:向上转型不能使用子类的特有功能。
B:解决方案是向下转型
多态成员访问的特点
当Fu fu = new Zi();
时,即左边是父类,右边是子类。
- 变量一律为:编译左边,运行左边
方法则需要分为两种情况
- 静态成员方法: 编译左边,运行左边
- 非静态成员方法:编译左边,运行右边
“编译和运行”的理解:比如编译看左边/右边,指的是去类中找指定的变量或者方法,编译在左就去左边找,编译在右就去右边找,能找到就编译正常,找不到代码就会报错。运行看左边/右边则指的是,如果子父类中有同名变量或者方法时,会优先调用谁。运行在左就调用父类,运行在右就调用子类。
规律:
- 编译都看的是左边
- 变量一律是编译左边,运行左边
- 只有非静态是编译左边,运行右边
多态中的向上转型和向下转型
- 向上转型:子类转为父类,自动转型。格式
Fu fu = new Zi();
,向上转型就像说狗是动物,连衣裙是衣服,我是人一样。以花木兰代父从军为例,花木兰冒充父亲就必须舍弃自己的姓名,以老爹的名字在军营过男儿的生活。即向上转型后,父类的引用fu所指向的属性是父类的属性。但是上阵杀敌的人的确是花木兰,即如果子类重写了父类的方法(如上阵杀敌),那么父类引用fu指向的或者调用的方法就是子类的方法,这个叫动态绑定。花木兰从军后必须摒弃女儿家的习惯,如娇羞和女红。即向上转型后,父类引用不能调用子类自己的方法(就是父类没有子类才有的方法)
总结:向上转型会丢失子类的新增方法,同时会保留子类重写的方法
- 向下转型:父类转为子类,需要强制转换。向下转型后就可以使用子类特有的功能了。就如同战争结束后,木兰又恢复了女儿身,可以娇羞,做女红,嫁人了。向下转型,既可以调用父类,也可以调用子类,但优先调用子类。
向下转型格式
Fu fu = new Zi();
Zi zi = (Zi) fu;
示例代码
动物类(父类)
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("动物在奔跑");
}
}
Cat类(子类)
class Cat extends Animal {
int num = 80;
static int age = 90;
String name = "tomCat";
public void eat() {
System.out.println("猫吃饭");
}
public static void sleep() {
System.out.println("猫在睡觉");
}
public void catchMouse() {
System.out.println("猫在抓老鼠");
}
}
测试类
class Demo_Test {
public static void main(String[] args) {
Animal am = new Cat();//向上转型
am.eat();//eat()是非静态成员方法,编译先看左边父类,但执行的是右边子类(右边)
am.sleep();//sleep()是静态成员方法,编译看左边父类,优先执行父类(左边)
am.run();//run()是父类方法,优先执行父类。(左)
// am.catchMouse(); 报错,catchMouse是子类非静态方法,编译是看左边父类的。父类没有,所以报错
//System.out.println(am.name); 报错,name是右边子类的非静态成员变量,编译看左边,左边没有name,所以报错
System.out.println(am.num);//num是非静态成员变量,编译看左边父类,运行看左边
System.out.println(am.age);//age是静态成员变量,编译看左边,运行看左边。
/*
* 总结:
* 当Fu fu = new Zi()时,即左边是父类,右边是子类
* 变量一律为:编译左边,运行左边
* 方法需要分为两种情况
* 静态成员方法: 编译左边,运行左边
* 非静态成员方法:编译左边,运行右边
*
* 规律:
* 1.编译都看的是左边
* 2.变量一律是编译左边,运行左边
* 3.只有非静态是编译左边,运行右边
* */
System.out.println("------------------------------");
Cat ct = (Cat)am;//向下转型,既可以调用父类,也可以调用子类,但优先调用子类。
ct.eat();
ct.sleep();
ct.run();
ct.catchMouse();
System.out.println(ct.name);
System.out.println(ct.age);
}
}
运行结果
猫吃饭
动物在睡觉
动物在奔跑
10
20
------------------------------
猫吃饭
猫在睡觉
动物在奔跑
猫在抓老鼠
tomCat
90