摘要:本文全面阐述了面向对象编程中多态这一关键特性,从多态的形式、使用场景、定义及前提条件出发,深入分析其运行特点、弊端,详细介绍引用类型转换相关知识,并通过综合练习强化对多态的理解与应用,为深入掌握面向对象编程提供了有力支撑。
关键词:多态;继承;方法重写;引用类型转换
参考资料::黑马程序员
一、引言
多态作为面向对象的第三大特性,在继承或实现关系中发挥重要作用,极大地增强了程序的灵活性与扩展性。本文将系统地探讨多态的各个方面。
二、多态的形式
多态在继承或实现关系中得以体现,其常见格式为:
父类类型 变量名 = new 子类/实现类构造器;
变量名.方法名();多态存在的前提是具有继承关系,子类对象能够赋值给父类类型变量。例如,Animal为动物类型,Cat为猫类型,Cat继承自Animal,那么Cat对象可赋值给Animal类型变量。
三、多态的使用场景
在未使用多态时,如register方法若仅能接收学生对象,对于Teacher和Administrator对象则需定义不同的register方法。
public void register(Student s){
}而借助多态,register方法的形参可定义为共同的父类Person。
 当方法形参为类时,可传递该类的所有子类对象;当形参为接口时,可传递该接口的所有实现类对象(后续学习),并且多态能够依据传递的不同对象调用不同类中的方法。
以代码示例说明:
// 父类
public class Person {
    private String name;
    private int age;
    // 空参构造
    public Person() {}
    // 带全部参数的构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // get和set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void show() {
        System.out.println(name + ", " + age);
    }
}
// 子类1
public class Administrator extends Person {
    @Override
    public void show() {
        System.out.println("管理员的信息为:" + getName() + ", " + getAge());
    }
}
// 子类2
public class Student extends Person {
    @Override
    public void show() {
        System.out.println("学生的信息为:" + getName() + ", " + getAge());
    }
}
// 子类3
public class Teacher extends Person {
    @Override
    public void show() {
        System.out.println("老师的信息为:" + getName() + ", " + getAge());
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        // 创建三个对象,并调用register方法
        Student s = new Student();
        s.setName("张三");
        s.setAge(18);
        Teacher t = new Teacher();
        t.setName("王建国");
        t.setAge(30);
        Administrator admin = new Administrator();
        admin.setName("管理员");
        admin.setAge(35);
        register(s);
        register(t);
        register(admin);
    }
    // 这个方法既能接收老师,又能接收学生,还能接收管理员
    // 只能把参数写成这三个类型的父类
    public static void register(Person p) {
        p.show();
    }
}PS:@Override 注解最主要的作用是帮助编译器进行检查,确保被注解的方法确实重写了父类中的方法。如果一个方法使用了 @Override 注解,但父类中不存在对应的方法,编译器会报错。这样可以避免因方法签名错误而导致的意外行为
四、多态的定义和前提
多态指同一行为具有多个不同表现形式。例如,Cat和Dog同为动物,都有“吃”这一行为,但具体表现不同。
 其前提条件如下:
- 继承或实现关系:类之间需存在继承或实现关系。
- 方法的重写:子类对父类方法进行重写,若无重写,多态意义不大。
- 父类引用指向子类对象:即采用父类类型 变量名 = new 子类类型();的格式,其中父类类型指子类对象继承的父类类型或实现的父接口类型。
五、多态的运行特点
- 调用成员变量时:编译阶段依据左边父类类型判断是否存在该成员变量,运行阶段也使用左边父类类型的成员变量值。
- 调用成员方法时:编译阶段检查左边父类是否存在该方法,运行阶段则执行右边子类重写后的方法。
以代码示例说明:
Fu f = new Zi();
// 编译看左边的父类中有没有name这个属性,没有就报错
// 在实际运行的时候,把父类name属性的值打印出来
System.out.println(f.name);
// 编译看左边的父类中有没有show这个方法,没有就报错
// 在实际运行的时候,运行的是子类中的show方法
f.show();六、多态的弊端
多态在编译阶段依赖左边父类类型,这导致若子类具有独有的功能,多态写法无法直接访问。例如:
class Animal {
    public void eat() {
        System.out.println("动物吃东西!");
    }
}
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
   
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
}
class Test {
    public static void main(String[] args) {
        Animal a = new Cat();
        a.eat();
        a.catchMouse();// 编译报错,编译看左边,Animal没有这个方法
    }
}七、引用类型转换
7.1 为什么要转型
多态无法直接调用子类独有的方法,为实现对这些方法的调用,需要进行转型。回顾基本数据类型转换,存在自动转换(范围小的赋值给范围大的,如double d = 5;)和强制转换(范围大的赋值给范围小的,如int i = (int)3.14)。多态的转型分为向上转型(自动转换)与向下转型(强制转换)。
7.2 向上转型(自动转换)
向上转型是子类类型向父类类型自动转换的过程,当父类引用指向子类对象时即发生向上转型,格式为:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();原因在于父类类型范围相对子类较大,例如Animal包含所有动物,而Cat只是其中一种,因此子类可自动转型给父类类型变量。
7.3 向下转型(强制转换)
向下转型是父类类型向子类类型的强制转换过程。对于已向上转型的子类对象,将父类引用转为子类引用需使用强制类型转换格式,即:
子类类型 变量名 = (子类类型) 父类变量名;
如:Aniaml a = new Cat();
   Cat c = (Cat) a;7.4 案例演示
为调用子类特有方法,需进行向下转型。以如下代码为例:
 定义类:
abstract class Animal {  
    abstract void eat();  
}  
class Cat extends Animal {  
    public void eat() {  
        System.out.println("吃鱼");  
    }  
    public void catchMouse() {  
        System.out.println("抓老鼠");  
    }  
}  
class Dog extends Animal {  
    public void eat() {  
        System.out.println("吃骨头");  
    }  
    public void watchHouse() {  
        System.out.println("看家");  
    }  
}定义测试类:
public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat
        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
    }  
}7.5 转型的异常
转型过程中可能出现问题,如下代码虽能编译通过,但运行时会抛出ClassCastException类型转换异常:
public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat
        // 向下转型  
        Dog d = (Dog)a;       
        d.watchHouse();        // 调用的是 Dog 的 watchHouse 【运行报错】
    }  
}这是因为创建的是Cat类型对象,运行时无法转换为Dog对象。
7.6 instanceof关键字
为避免ClassCastException异常(ClassCastException 是 Java 中的一个运行时异常,当你尝试将一个对象强制转换为不兼容的类型时就会抛出该异常。),Java提供instanceof关键字用于校验引用变量类型,格式为:
变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回false。因此,转换前可先进行判断,代码如下:
public class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat
        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}7.7 instanceof新特性
JDK14引入新特性,可将判断和强转合并为一行:
// 新特性
// 先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
// 如果不是,则不强转,结果直接是false
if(a instanceof Dog d){
    d.lookHome();
} else if(a instanceof Cat c){
    c.catchMouse();
} else{
    System.out.println("没有这个类型,无法转换");
}八、综合练习
8.1 需求
- 定义狗类:包含年龄、颜色属性,以及eat(String something)(表示吃的东西)和看家lookHome(无参数)方法。
- 定义猫类:包含年龄、颜色属性,以及eat(String something)和逮老鼠catchMouse(无参数)方法。
- 定义Person类(饲养员):包含姓名、年龄属性,以及keepPet(Dog dog, String something)(喂养宠物狗)和keepPet(Cat cat, String something)(喂养宠物猫)方法,生成空参、有参构造及set和get方法。
- 定义测试类:实现keepPet(Dog dog, String something)方法打印“年龄为30岁的老王养了一只黑颜色的2岁的狗,2岁的黑颜色的狗两只前腿死死的抱住骨头猛吃”;keepPet(Cat cat, String something)方法打印“年龄为25岁的老李养了一只灰颜色的3岁的猫,3岁的灰颜色的猫眯着眼睛侧着头吃鱼”。
- 思考:
- 
Dog和Cat均为Animal的子类,现有针对不同动物定义不同keepPet方法的方式较为繁琐,思考如何简化及体会简化后的好处。
- 尽管Dog和Cat是Animal的子类,但都有特有方法,思考如何在keepPet中调用这些特有方法。
8.2 代码示例
// 动物类(父类)
public class Animal {
    private int age;
    private String color;
    public Animal() {}
    public Animal(int age, String color) {
        this.age = age;
        this.color = color;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public void eat(String something) {
        System.out.println("动物在吃" + something);
    }
}
// 猫类(子类)
public class Cat extends Animal {
    public Cat() {}
    public Cat(int age, String color) {
        super(age, color);
    }
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的猫眯着眼睛侧着头吃" + something);
    }
    public void catchMouse() {
        System.out.println("猫抓老鼠");
    }
}
// 狗类(子类)
public class Dog extends Animal {
    public Dog() {}
    public Dog(int age, String color) {
        super(age, color);
    }
    // 行为
    // eat(String something)(something表示吃的东西)
    // 看家lookHome方法(无参数)
    @Override
    public void eat(String something) {
        System.out.println(getAge() + "岁的" + getColor() + "颜色的狗两只前腿死死的抱住" + something + "猛吃");
    }
    public void lookHome() {
        System.out.println("狗在看家");
    }
}
// 饲养员类
public class Person {
    private String name;
    private int age;
    public Person() {}
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // 饲养狗
    /*public void keepPet(Dog dog, String something) {
        System.out.println("年龄为" + age + "岁的" + name + "养了一只" + dog.getColor() + "颜色的" + dog.getAge() + "岁的狗");
        dog.eat(something);
    }
    // 饲养猫
    public void keepPet(Cat cat, String something) {
        System.out.println("年龄为" + age + "岁的" + name + "养了一只" + cat.getColor() + "颜色的" + cat.getAge() + "岁的猫");
        cat.eat(something);
    }*/
    // 想要一个方法,能接收所有的动物,包括猫,包括狗
    // 方法的形参:可以写这些类的父类 Animal
    public void keepPet(Animal a, String something) {
        if(a instanceof Dog d) {
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + a.getColor() + "颜色的" + a.getAge() + "岁的狗");
            d.eat(something);
        } else if(a instanceof Cat c) {
            System.out.println("年龄为" + age + "岁的" + name + "养了一只" + c.getColor() + "颜色的" + c.getAge() + "岁的猫");
            c.eat(something);
        } else {
            System.out.println("没有这种动物");
        }
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        // 创建对象并调用方法
        /*Person p1 = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        p1.keepPet(d,"骨头");
        Person p2 = new Person("老李",25);
        Cat c = new Cat(3,"灰");
        p2.keepPet(c,"鱼");*/
        // 创建饲养员的对象
        Person p = new Person("老王",30);
        Dog d = new Dog(2,"黑");
        Cat c = new Cat(3,"灰");
        p.keepPet(d,"骨头");
        p.keepPet(c,"鱼");
    }
}                
 
 
                     
            
        













 
                    

 
                 
                    