多态继承的运行时绑定

今天在博客上看见一道多态的执行顺序题,如下

public class Dervied extends Base {

    private String name = "dervied";

    public Dervied() {
        tellName();
        printName();
    }
   
    public void tellName() {
        System.out.println("Dervied tell name: " + name);
    }
    
    public void printName() {
        System.out.println("Dervied print name: " + name);
    }

    public static void main(String[] args){
        
        new Dervied();    
    }
}

class Base {
    
    private String name = "base";

    public Base() {
    
        tellName();
        printName();
    }
    
    public void tellName() {
        System.out.println("Base tell name: " + name);
    }
    
    public void printName() {
        System.out.println("Base print name: " + name);
    }
}

我原本以为答案是这样的:

Base tell name: null
Base print name: null
Dervied tell name: dervied
Dervied print name: dervied



然而运行后结果却是:

Dervied tell name: null
Dervied print name: null
Dervied tell name: dervied
Dervied print name: dervied

也就是说,在执行父类构造方法的时候,已经执行子类重写后的方法了。

根据我的理解,应该是运行时绑定在作祟,当子类新建对象时,父类调用被子类重写过的方法,那么编译器就会判定,应该将该方法编译为子类重写的方法。

我想了好久,除了这个解释应该没别的原因,如果我错了,请私信我。

啊,本来每天11.30准时睡觉的,为了解决这个问题拖了一小时还没洗澡.....不过蛮开心的,碎觉。
<think>我们正在讨论面向对象编程中的多态运行动态绑定。根据引用内容,多态分为运行多态和编译多态运行多态通过虚函数(或类似机制)实现,而编译多态通过函数重载实现。动态绑定发生在运行,根据对象的实际类型决定调用哪个方法;静态绑定则在编译确定。引用[1]和[2]解释了多态的两种类型:运行多态(动态多态)和编译多态(静态多态)。动态多态通过覆盖虚函数实现,在运行根据对象实际类型决定调用哪个方法。静态多态通过重载实现,在编译根据参数类型、数量等确定调用哪个方法。引用[3]和[4]给出了具体的代码示例,展示了运行多态(动态绑定)的工作方式:当基类指针或引用指向派生类对象,调用虚函数会执行派生类中的重写版本。现在,我将详细解释运行多态和动态绑定:1.**多态(Polymorphism)**:在面向对象编程中,多态是指同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。多态分为两种:- **编译多态(静态绑定)**:通过函数重载和运算符重载实现,在编译阶段确定具体调用哪个函数。- **运行多态(动态绑定)**:通过继承和虚函数(或类似机制)实现,在程序运行根据对象的实际类型确定调用哪个函数。2. **运行多态(动态多态)**:其核心在于使用基类的指针或引用指向派生类对象,并通过该指针或引用调用虚函数。在运行,系统会根据对象的实际类型(而不是指针或引用的类型)来调用相应的函数。这需要满足两个条件:-基类中声明虚函数(在C++中使用`virtual`关键字,在Java中所有非静态方法默认是虚函数)。-派生类重写(override)该虚函数。3.**动态绑定(Dynamic Binding)**:也称为晚期绑定(Late Binding),是指在程序运行期间(而非编译期间)根据对象的实际类型来确定调用哪个函数。动态绑定是实现运行多态的基础。4. **示例分析**(引用[4]中的Java代码,但原理与C++相同):```javaclass Animal1 {publicString name;public intage;publicAnimal1(String name, intage){this.name= name;this.age= age;}public voideat(){System.out.println(this.name +"正在吃东西");}}classDog1extends Animal1 {publicDog1(String name, intage){super(name, age);}public voideat(){//重写父类的eat方法System.out.println(this.name +"吃狗粮");}}class Cat1 extendsAnimal1{public Cat1(Stringname,int age) {super(name,age);}publicvoid eat() {//重写父类的eat方法System.out.println(this.name+ "吃猫粮");}}publicclass demot {publicstatic voidmain(String[] args) {Animal1 animal= newDog1("小黄",2);//父类引用指向子类对象animal.eat();//输出:小黄吃狗粮}}```在这个例子中:-我们创建了一个`Animal1`类型的引用`animal`,但它指向的是`Dog1`类的对象。-当调用`eat()`方法,由于在Java中非静态方法默认是虚函数(相当于C++中的`virtual`),所以会动态绑定到`Dog1`类的`eat()`方法,输出“小黄吃狗粮”。5. **动态绑定的实现机制**(以C++为例):-当类中有虚函数,编译器会为该类生成一个虚函数表(vtable),表中存放虚函数的地址。-每个对象在创建,会包含一个指向虚函数表的指针(vptr)。-当通过基类指针或引用调用虚函数,程序会通过对象的vptr找到虚函数表,再根据函数在表中的位置找到实际要调用的函数地址。这一过程在运行完成。6.**动态绑定的优点**:-提高代码的灵活性和可扩展性:可以在不修改现有代码的情况下,通过派生类扩展功能。-实现接口重用:基类定义接口,派生类实现具体功能。7. **动态绑定的缺点**:-运行开销:每次调用虚函数都需要通过虚函数表查找,比直接调用函数稍慢。-对象占用空间增加:每个对象需要存储一个虚函数表指针。8. **与静态绑定的对比**:|特性|静态绑定|动态绑定||--------------|------------------------------|------------------------------||绑定间|编译|运行||实现机制|函数重载、模板|虚函数||性能|高效(无额外开销)|有运行开销||灵活性|较低|高||适用场景|编译确定类型的方法调用|运行确定类型的方法调用|总结:运行多态(动态绑定)是面向对象编程的核心特性之一,它允许程序在运行根据对象的实际类型来调用相应的方法,提高了代码的灵活性和可扩展性。实现运行多态的关键是使用虚函数(或类似机制)和继承。 §§相关问题§§1.在C++中,虚函数表(vtable)的具体结构是怎样的? 2.动态绑定在多重继承中如何工作?3.除了虚函数,还有哪些机制可以实现运行多态? 4.动态绑定在Java和C++中的实现有何异同?</think>### 面向对象编程中的多态运行动态绑定详解 #### 1. **多态的核心概念** 多态(Polymorphism)是面向对象三大特性之一,指**同一操作作用于不同对象产生不同行为**。分为两类: - **编译多态(静态绑定)** 通过函数重载实现,编译器根据**参数类型和数量**在编译阶段确定调用哪个方法。 ```java // 函数重载示例 void print(int x) { ... } // 版本1 void print(String s) { ... } // 版本2 ``` 调用 `print(5)` 编译器直接绑定版本1 [^1][^2]。 - **运行多态(动态绑定)** 通过继承和虚函数实现,**运行**根据对象的**实际类型**决定调用哪个方法。 ```java Animal animal = new Dog(); // 父类引用指向子类对象 animal.eat(); // 实际调用Dog.eat() ``` 输出结果:"小黄吃狗粮"(见引用[4])[^3][^4]。 #### 2. **运行动态绑定机制** 动态绑定运行多态的实现基础,包含三个核心要素: 1. **继承关系** 子类继承父类(如 `Dog extends Animal`) 2. **虚函数(或等效机制)** 父类方法声明为 `virtual`(C++)或默认虚函数(Java) 3. **向上转型** 父类引用指向子类对象(`Animal animal = new Dog()`) **执行过程**: ```mermaid graph TD A[调用animal.eat] --> B{检查对象实际类型} B -->|实际是Dog| C[调用Dog.eat] B -->|实际是Cat| D[调用Cat.eat] ``` - 当调用 `animal.eat()` ,JVM/C++运行会: 1. 查找 `animal` 引用的**实际对象类型**(非声明类型) 2. 根据实际类型(如 `Dog`)调用其重写的方法 [^3][^4] #### 3. **关键特性对比 | **特性** | **静态绑定(编译多态)** | **动态绑定运行多态)** | |----------------|----------------------------------|----------------------------------| | **绑定机** | 编译阶段 | 运行阶段 | | **决定因素** | 参数类型/数量 | 对象实际类型 | | **实现方式** | 函数重载 | 虚函数+继承 | | **性能** | 高效(直接调用) | 有开销(需运行查表) | | **灵活性** | 低(行为在编译固定) | 高(行为可运行扩展) | #### 4. **设计意义与价值 1. **接口统一性** ```java void makeSound(Animal animal) { animal.speak(); // 统一接口,支持所有Animal子类 } ``` 调用 `makeSound(new Dog())` 和 `makeSound(new Cat())` 产生不同行为 [^3]。 2. **可扩展性** 新增子类(如 `Bird`)无需修改现有代码,符合开闭原则。 3. **降低耦合** 调用方只需关注父类接口,无需知道具体子类实现。 #### 5. **动态绑定实现原理(以Java为例) - **虚方法表(vtable)** 每个类维护一个虚方法表,存储可重写方法的实际地址。 - **对象头中的类型指针** 对象内存布局包含指向类信息的指针,运行通过该指针查找虚方法表。 - **调用过程伪代码**: ```java // 运行解析调用 Method method = object.getClass().getMethod("eat"); method.invoke(object); // 动态调用实际对象的方法 ``` > **关键总结**:运行多态通过**延迟绑定**将方法调用与实现分离,使程序能根据上下文自适应调整行为,这是设计模式(如策略模式、工厂模式)的基础[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值