面向对象:多态

面向对象:多态

在面向对象编程中,多态是三大核心特性(封装、继承、多态)之一,也是实现代码复用、提高程序扩展性的关键。掌握多态,能让你的代码更具优雅性和可维护性,尤其在大型项目开发中,多态的价值会体现得淋漓尽致。

简单来说,多态就是:同一件事情发生在不同的对象上,会产生不同的结果

举个生活中的通俗例子:同样是「“打招呼”」这个行为,中国人可能会说“你好”,美国人可能会说“Hello”,法国人可能会说“Bonjour”——行为相同,执行主体(对象)不同,结果也就不同,这就是生活中的“多态”。

在 Java 中,这个概念同样适用:比如一个「“吃饭”」方法,调用在“猫”对象上就是“吃猫粮”,调用在“狗”对象上就是“吃狗粮”,调用在“人”对象上就是“吃米饭”,这就是 Java 中的多态。


一、多态的实现条件

多态并非凭空存在,它的实现需要满足三个核心条件,三者缺一不可,就像盖房子需要地基、墙体、屋顶一样,缺少任何一个都无法建成完整的房屋。

  1. 必须处于继承体系之下(包括类的直接/间接继承,或接口的实现)
  2. 子类(或实现类)必须对父类(或接口)的方法进行重写(Override)
  3. 必须通过父类(或接口)的引用来调用被重写的方法

这里需要额外说明:接口的实现本质上可以看作一种“特殊的继承”,因此实现接口并覆写接口中的抽象方法,也满足多态的实现条件,这也是实际开发中多态的常用场景之一。


二、重写(Override)

要实现多态,重写是核心环节。如果说继承是多态的“前提”,那么重写就是多态的“灵魂”,没有重写,就无法体现“同一行为,不同结果”的多态特性。

代码示例

// 父类:动物
public class Animal {
    // 动物名称
    public String name;
    // 动物年龄
    public int age;

    // 构造方法:初始化动物名称
    public Animal(String name) {
        this.name = name;
    }

    // 动物的吃饭行为(父类默认实现)
    public void eat() {
        System.out.println(this.name + "正在进食");
    }

    // 动物的睡觉行为(父类默认实现)
    public void sleep() {
        System.out.println(this.name + "正在睡觉");
    }
}

// 子类:猫(继承自动物类)
public class Cat extends Animal{

    // 子类构造方法,调用父类构造方法初始化名称
    public Cat(String name) {
        super(name);
    }

    // 重写父类的eat方法,体现猫的特有进食行为
    @Override
    public void eat() {
        System.out.println(this.name + "正在吃猫粮,细嚼慢咽中~");
    }
}

重写方法的核心规则

重写不是随意修改父类方法,而是需要遵循严格的“约定”,这些约定是保证多态能够正常生效的关键,总结为以下 4 点:

  1. 子类重写父类方法时,方法名、参数列表(类型、个数、参数顺序) 必须与父类完全一致(如果参数列表不同,就不是重写而是重载了)
  2. 子类重写方法的返回值类型,要么与父类方法返回值类型完全一致,要么是父类返回值类型的子类(即协变返回类型)
    • 示例:父类方法返回 Animal,子类重写后可以返回 CatCatAnimal 的子类)
  3. 子类重写方法的访问权限,不能比父类对应的方法更严格(可以更宽松或保持一致)
    • 示例:父类方法是 public,子类重写后不能是 protectedprivate;父类方法是 protected,子类重写后可以是 publicprotected
  4. 特殊修饰符限制:父类被 privatestaticfinal 修饰的方法无法被重写
    • private 方法:父类私有方法对子类不可见,子类无法访问也就无法重写
    • static 方法:静态方法属于类本身,不属于对象,而多态是基于对象的,因此无法重写(子类可以定义同名静态方法,但这是“隐藏”而非“重写”)
    • final 方法:final 关键字的作用是“禁止修改”,被 final 修饰的方法无法被子类重写,常用于保护核心方法不被篡改

三、重写与重载的核心对比

在 Java 中,重写(Override)和重载(Overload)是两个容易混淆的概念,二者都属于方法的特殊特性,但本质和用途截然不同。下面通过表格清晰对比二者的核心区别:

区别维度重写(Override)重载(Overload)
访问限定符不能比父类方法更严格(可宽松/一致)可以自由修改,无强制限制
返回值类型基本不变(支持协变返回类型)可以自由修改,无强制限制
参数列表不能修改(方法名+参数列表需完全一致)必须修改(个数/类型/参数顺序至少有一项不同)
绑定类型动态绑定(运行时确定调用哪个方法)静态绑定(编译时就确定调用哪个方法)
存在范围仅存在于父子类(或接口与实现类)的继承体系中仅存在于同一个类中(包括子类继承的父类方法与子类自身方法的重载)
核心用途实现多态,体现对象的特有行为方便用户调用同一功能的方法,简化方法命名

补充说明:

  • 动态绑定:程序运行时,根据对象的实际类型来确定要调用的方法,这是多态的核心底层支撑
  • 静态绑定:程序编译时,就已经确定要调用的方法,与对象实际类型无关

四、向上转型

在满足继承和重写的条件后,要实现多态还需要最后一步——向上转型,它是多态的“载体”,也是实现多态的必要操作,且向上转型本身是安全无风险的。

概念说明

向上转型,就是创建一个子类对象,将其赋值给父类类型的引用变量,本质是父类引用指向子类对象

由于子类是对父类的扩展,子类包含了父类的所有属性和方法(非私有),因此将子类对象赋值给父类引用,就像“把一个小杯子里的水倒进一个大杯子里”,不会出现溢出,所以是安全的。

向上转型后,父类引用只能访问父类中定义的属性和方法(包括被子类重写的方法),无法访问子类独有的属性和方法。

语法格式

// 语法:父类类型  引用变量名 = new 子类类型();
父类类型  对象名 = new 子类类型();

代码示例

// 父类:Animal(延续上文的定义)
// 子类:Dog(新增一个狗类,继承自Animal并改写eat方法)
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    // 重写父类的eat方法,体现狗的特有进食行为
    @Override
    public void eat() {
        System.out.println(this.name + "正在啃骨头,嘎嘣脆~");
    }
}

// 测试类:演示向上转型
public class PolymorphismTest {
    public static void main(String[] args) {
        // 向上转型:父类Animal引用指向子类Cat对象
        Animal cat = new Cat("小花猫");
        // 调用的是Cat子类重写后的eat方法(体现多态)
        cat.eat();
        
        // 向上转型:父类Animal引用指向子类Dog对象
        Animal dog = new Dog("大黄狗");
        // 调用的是Dog子类重写后的eat方法(体现多态)
        dog.eat();
    }
}

运行结果

小花猫正在吃猫粮,细嚼慢咽中~
大黄狗正在啃骨头,嘎嘣脆~

从运行结果可以看出,同样是 Animal 类型的引用,调用 eat() 方法时,根据指向的子类对象不同,产生了不同的结果,这就是多态的直观体现。


五、向下转型

向上转型虽然安全且能实现多态,但也有一个局限:父类引用无法访问子类独有的属性和方法。如果我们需要使用子类的特有功能,就需要将父类引用还原为子类类型,这就是向下转型。

概念说明

向下转型,是将已经完成向上转型的父类引用,转换回子类类型的操作,本质是将“父类引用”还原为“子类对象”,从而能够访问子类独有的属性和方法。

注意:向下转型存在类型不匹配的风险,就像“把大杯子里的水倒进小杯子里,可能会溢出”一样,如果父类引用指向的不是当前子类类型的对象,强行向下转型会抛出 ClassCastException(类型转换异常)。

为了保证转型安全,通常需要配合 instanceof 关键字进行类型判断,先判断父类引用指向的对象是否为目标子类类型,再进行转型。

代码示例

// 先补充子类的特有方法
public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在吃猫粮,细嚼慢咽中~");
    }

    // 猫的特有方法:喵喵叫
    public void mew() {
        System.out.println(this.name + "喵喵叫,求主人摸摸头~");
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void eat() {
        System.out.println(this.name + "正在啃骨头,嘎嘣脆~");
    }

    // 狗的特有方法:汪汪叫
    public void bark() {
        System.out.println(this.name + "汪汪叫,警惕陌生人靠近~");
    }
}

// 测试类:演示向下转型与安全判断
public class DownCastTest {
    public static void main(String[] args) {
        // 1. 先完成向上转型
        Animal animal = new Cat("小花猫");
        animal.eat(); // 调用Cat重写的eat方法

        // 2. 尝试调用子类特有方法(直接调用会报错,父类引用无法访问子类特有方法)
        // animal.mew(); // 编译报错

        // 3. 向下转型(配合instanceof做安全判断)
        if (animal instanceof Cat) {
            // 转型成功,还原为Cat类型
            Cat cat = (Cat) animal;
            // 可以调用Cat的特有方法
            cat.mew();
        }

        // 4. 切换父类引用指向的对象
        animal = new Dog("大黄狗");
        animal.eat(); // 调用Dog重写的eat方法

        // 5. 再次向下转型,判断是否为Dog类型
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.bark();
        }
    }
}

运行结果

小花猫正在吃猫粮,细嚼慢咽中~
小花猫喵喵叫,求主人摸摸头~
大黄狗正在啃骨头,嘎嘣脆~
大黄狗汪汪叫,警惕陌生人靠近~
面向对象:继承 2025-07-05
重写部分Object方法 2025-07-10

评论区