1.多态概述
多态是面向对象语言三大特征之一,多态是指对象的多种形态,就是同类型的对象,表现出的不同形态。
多态的前提:有继承关系,有父类引用指向子类对象,有方法重写
多态的表现形式:父类类型 对象名称=子类对象;
1.1多态的场景应用
在一个球类信息管理系统当中,我们要去写一个方法查看各种各样球的信息
是每一种球都写一种方法查看信息呢?还是只写一种方法去查看所有的球的信息好呢?
答案肯定是只写一种
接下来简单的实现一个球类信息查看的系统
class Ball{
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public void show(){
}
}
class pingpong extends Ball{
public void show(){
System.out.println("乒乓球的价格为"+getPrice()+"元");
}
}
class badminton extends Ball{
public void show(){
System.out.println("羽毛球的价格为"+getPrice()+"元");
}
}
class basketball extends Ball{
public void show(){
System.out.println("篮球的价格为"+getPrice()+"元");
}
}
class football extends Ball{
public void show(){
System.out.println("足球的价格为"+getPrice()+"元");
}
}
public class T2 {
public static void main(String[] args) {
Ball b1=new pingpong();
b1.setPrice(2);
show(b1);
Ball b2=new badminton();
b2.setPrice(4);
show(b2);
}
public static void show(Ball p){
p.show();
}
}
当我们将乒乓球和羽毛球的对象赋值给父类的对象时,将父类类型作为查看信息的方法的形参,用父类类型的变量调用show方法时,会发现父类类型的变量会调用不同子类的show方法,这就是多态的一种应用场景
2.多态调用成员的特点
1.变量调用:编译看左边,运行也看左边
2.方法调用:编译看左边,运行看右边
2.1变量调用
class Animal{
}
class Dog extends Animal{
String name;
}
public class T3 {
public static void main(String[] args) {
Animal ani=new Dog();
ani.name="张三";
}
}
编译看左边:javac编译代码的时候会看左边的父类里面有没有这个变量,如果有,则成功编译,否则编译失败。上面代码因为父类里面没有name这个变量,所以会编译失败
class Animal{
String name="动物";
}
class Dog extends Animal{
String name="狗";
}
public class T3 {
public static void main(String[] args) {
Animal ani=new Dog();
System.out.println(ani.name);
}
}
运行也看左边:我们分别给父类和子类的name赋不同值时,在用ani调用name时,输出的结果是动物,也就是运行时ani调用的是父类的name
2.2方法调用
class Animal{
}
class Dog extends Animal{
public void show(){
System.out.println("Dog的show");
}
}
public class T3 {
public static void main(String[] args) {
Animal ani=new Dog();
ani.show();
}
}
编译看左边:和变量调用一样,ani调用show方法时,父类里面有show,则编译成功,没有则编译失败
class Animal{
public void show(){
System.out.println("Animal的show");
}
}
class Dog extends Animal{
public void show(){
System.out.println("Dog的show");
}
}
public class T3 {
public static void main(String[] args) {
Animal ani=new Dog();
ani.show();
}
}
运行看右边:Animal和Dog类里面都有show方法时,我们使用ani调用show方法时,ani会调用子类Dog里面的show方法
2.3对方法调用和变量调用的理解
成员变量:
在子类的对象中,会把父类的成员变量继承下来
当我们Animal a=new Dog();时,父类里面有一个name,子类里面也有一个name,这两个name在对象里面都是存在的
之前我们用Dog dog=new Dog()创建一个对象时,用dog调用name,dog是Dog类型,他会去Dog类里面寻找name,现在用Animal a=new Dog();创建a对象,a是Animal类型,a调用name,那么他就会去Animal里面去寻找name
成员方法:
当我们运用多态去调用方法时,子类里面的方法其实是对父类方法的重写,而子类继承父类方法时继承父类的虚方法表,当子类对父类方法进行重写时,会将父类方法在虚方法表中的虚方法覆盖,从而在用ani.show时,调用的是子类的方法
2.4多态调用成员的内存图解
1.测试类的字节码文件T3.class先加载到方法区当中,java虚拟机会自动的将main方法加载进栈
2.在main方法的第一行代码,即用到了Animal类又用到了Dog类,在Java中,永远是先加载父类,再加载子类,Java中有一个所有类的祖宗类(object类),这里object没太大关系,所以在图中就不画了,在方法区中先加载Animal.class,再加载Dog.class。
(在字节码文件中会加载所有的成员变量和成员方法,在成员方法的下面会挂一个需方法表,子类的重写方法会将继承下来的虚方法表中父类show方法的虚方法覆盖)
(子类会和父类有联系,他会记录父类字节码文件的位置)
3.第一行代码等号的左边会在栈内存里面开辟一个Animal类型,名为ani的空间,等号右边,new关键字代表在堆内存里面开辟了一个小空间,在对象里面,他会将这个空间分为两部分,一部分用来存储在父类继承下来的成员信息,另一部分会存储自己的成员信息,假设这块堆内存的地址值为1900,栈内存里面ani的空间会存储1900这个地址值,ani可以通过1900找到堆内存里面的变量
4.第二行代码sout(ani.name),这时就编译看左边,运行也看左边,看1900里面父类是否有name,有就编译成功,否则失败,ani是Animal类型的,他直接去父类里面调用name
5.第三行代码,编译看左边,运行看右边,编译时他会去看父类里面有没有show,有则编译成功,否则编译失败。在运行时他会去子类里面找show方法,因为子类重新将需方法表中的show方法覆盖,所以调用的是子类的show方法
3.多态的优势和弊端
优势:
1.在多态形式下,右边对象可以实现解耦合,便于扩展和维护
举例:
Animal a=new Dog();
a.eat;
当我后面想要Cat的eat时,只需将new Dog()改成new Cat(),就可完成业务逻辑的修改,无需将后续的代码修改
2.定义方法的时候,使用父类类型作为参数,可以接收所有的子类对象,体现多态的扩展性和便利
弊端:
1.不能调用子类的特有方法
解决方案:强制转换,将调用者变回子类类型,转换时不能瞎转,即Animal dog=new Dog();不能将dog转成Cat类型,瞎转会报类型转换异常
补充:instanceof关键字
instanceof关键字是判断某一个对象是不是某个类型
格式:
对象名 instanceof 类名
如果这个对象的类型是后面那个类,那么整个结果是true 否则是false