重写和重载:它们和多态有啥关系?

235 阅读7分钟

1. 什么是多态?

  • 官方答案: 多态是面向对象编程中的一个重要概念,它允许通过父类类型引用变量引用子类对象,并在运行时根据实际对象的类型来确定调用哪个方法。

  • 个人理解: 多态,简单来说,就是“同一个动作在不同情境下,可以表现出不同的行为”
    在编程中,多态是面向对象编程的一大特性,指的是同一个接口(或者方法调用)可以表现出不同的实现方式。

日常生活中的例子:

  • “动物类”都有一个动作叫做“发出声音”,但具体到“狗”是“汪汪叫”,“猫”是“喵喵叫”,“牛”是“哞哞叫”。虽然它们发出声音的方法名都叫makeSound(),但不同的动物表现出来的行为是不同的。

在代码中,这样的多样性就是多态。


2.多态的特性

  1. 代码的可扩展性

    • 多态允许通过父类引用操作不同的子类对象,增强代码的灵活性和可扩展性。
    • 当需要添加新功能(比如添加新的子类),只需实现新的子类并重写方法,而无需修改现有代码。
  2. 运行时决定行为

    • 具体调用哪个方法由对象的实际类型决定,而不是编译时的类型。这就是动态绑定的核心特性。
  3. 统一接口

    • 父类提供统一的接口(方法名和参数相同),而不同子类根据需要提供不同的实现。
    • 这让调用方只需要关心接口而不需要关心具体实现。

3.多态的优点

  1. 提高代码的通用性、灵活性和可维护性

    • 使用多态后,程序可以操作父类引用,而不用关心具体的子类对象。
      例如,处理“不同动物的叫声”时,只需调用统一的 makeSound() 方法,而不关心具体是“狗”还是“猫”。
    • 提高了代码的可读性和可维护性,减少重复代码。

    例子:

    void makeAnimalSound(Animal animal) {
        animal.makeSound(); // 无论传入的是Dog还是Cat,都会根据实际类型执行
    }
    
  2. 设计模式的基础

    • 多态是多个经典设计模式(如工厂模式策略模式等)的基础,让代码更符合“开闭原则”(对扩展开放,对修改关闭)。
  3. 解耦代码

    • 调用者只需要认识父类或接口,不用直接依赖具体的子类实现,降低了模块之间的耦合度。
    • 例如,用接口或抽象类定义方法,其他类只需实现这些方法即可,调用不受影响。

4.多态的缺点

  1. 性能开销

    • 多态的实现需要依赖动态绑定(运行时决定调用哪个方法),相比直接调用方法会有一定的性能开销。
    • 特别是在嵌套层级较深时,效率会受到一定影响。不过现代编译器已经优化了这个问题。
  2. 代码调试复杂

    • 因为多态是在运行时动态决定具体方法的调用,调试时可能需要跟踪多个子类的实现代码,这增加了排查问题的难度。
  3. 过度依赖继承可能导致设计复杂

    • 如果设计不好,父类和子类之间的关系会变得复杂,可能引入一些意外的行为。
    • 例如,如果子类没有正确实现父类的方法逻辑,可能会导致出人意料的结果。
  4. 不适用于所有场景

    • 多态适用于“行为多样化”的场景,但在“行为固定”“无需动态决定行为”的场景下,可能显得多余。

  • 特性:灵活性、代码复用、统一接口、运行时动态绑定。
  • 优点:增强代码可扩展性、提高代码复用性、降低耦合性。
  • 缺点:性能开销、调试复杂、设计可能变得冗余。

5. 多态的实现原理?

多态的实现基于以下关键点:

  1. 继承:子类继承父类,保证子类可以拥有父类的方法。
  2. 方法重写:子类可以根据自己的特点,重新实现父类中的方法。
  3. 父类引用指向子类对象:通过父类的引用调用方法,具体执行的是子类的实现内容。

例如:

class Animal {
    void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("汪汪叫");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("喵喵叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 父类引用指向子类对象
        Animal animal2 = new Cat();
        
        animal1.makeSound(); // 输出:汪汪叫
        animal2.makeSound(); // 输出:喵喵叫
    }
}

在这个例子中,animal1animal2 都是 Animal 类型,但它们在运行时表现出不同的行为,这是因为方法被子类重写,实现了多态。


6. 多态的实现方式?

多态有两种主要实现方式:

  1. 编译时多态(静态多态)

    • 通过方法重载(Overloading)实现。
    • 方法名相同,但参数个数或类型不同。
    • 多态的行为在编译时就已经确定。

    例子:

    class Calculator {
        int add(int a, int b) {
            return a + b;
        }
        double add(double a, double b) {
            return a + b;
        }
    }
    
  2. 运行时多态(动态多态)

    • 通过方法重写(Overriding)实现。
    • 子类重写父类的方法,父类引用指向子类对象时,根据实际对象类型在运行时决定调用哪个方法。
    • 动态多态在运行时才确定行为。

    例子:

    Dog dog = new Dog();
    Animal animal = dog; // 父类引用
    animal.makeSound(); // 输出:汪汪叫
    

7. “方法重写和重载”与多态的关系?

  • 方法重写(Overriding) 是实现运行时多态的基础,它允许子类定义自己的方法行为。

    • 通过重写,子类可以改变父类的方法实现,从而在运行时表现出不同的行为。
  • 方法重载(Overloading) 是实现编译时多态的方式,方法名相同但参数列表不同。

    • 这种多态性在编译阶段就能确定。

总结:

  • 重写 = 运行时多态。
  • 重载 = 编译时多态。

8. 什么是动态绑定和虚拟方法调用?

  • 动态绑定(Dynamic Binding)

    • 指的是在程序运行时,Java 编译器只能知道变量的声明类型,而无法确定其实际的对象类型。根据对象的实际类型确定调用哪个方法。
    • 动态绑定是实现运行时多态的关键机制。Java 虚拟机(JVM)会通过动态绑定来解析实际对象的类型。即便父类引用指向子类对象,调用方法时依然会根据子类的实际实现来执行。
  • 虚拟方法调用(Virtual Method Invocation)

    • 当一个方法被标记为“虚拟的”(默认情况下,在 Java 中,所有的非私有非静态非 final非static 方法都是被隐式地指定为虚拟方法。),调用时会基于实际对象的类型而非引用的类型。
    • 这是动态绑定的具体表现。

例如:

class Parent {
    void show() {
        System.out.println("父类方法");
    }
}

class Child extends Parent {
    @Override
    void show() {
        System.out.println("子类方法");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent obj = new Child(); // 父类引用指向子类对象
        obj.show(); // 输出:子类方法(动态绑定)
    }
}

在这里,obj.show() 是虚拟方法调用,程序在运行时动态绑定到子类的 show() 方法。


总结:

  1. 多态:指同一个方法在不同对象中表现出不同的行为。
  2. 实现原理:继承 + 方法重写 + 父类引用指向子类对象。
  3. 实现方式:包括编译时多态(方法重载)和运行时多态(方法重写)。
  4. 动态绑定/虚拟方法调用:运行时根据实际对象决定调用哪个方法,支持多态性。