静态与动态绑定:继承时为何字段来自父类,而方法属于子类?

108 阅读4分钟

在 Java 的继承机制中,**字段(成员变量)方法(成员函数)**的行为有所不同,这是因为 Java 的类继承机制对它们的处理方式不同。

代码示例:

public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.shout();  // 方法调用,输出子类中的实现
        System.out.println(dog.name);  // 字段访问,输出父类中的字段
    }
}

假设我们有如下的类定义:

class Animal {
    String name = "Animal";

    void shout() {
        System.out.println("Animal shout");
    }
}

class Dog extends Animal {
    String name = "Dog";  // Dog类中的name字段

    @Override
    void shout() {
        System.out.println("Dog bark");
    }
}

为什么字段是父类的,而方法是子类的?

  1. 字段(成员变量)是父类的:

    • 在 Java 中,字段是静态绑定的,也就是说,字段的访问取决于引用变量的类型,而不是实际对象的类型。
    • 在代码中,dogAnimal 类型的引用,它指向一个 Dog 类型的实例。由于字段在 Java 中并不具备多态性(即不会根据对象的实际类型动态查找字段),所以 dog.name 会访问 Animal 类中的 name 字段,而不是 Dog 类中的 name 字段。
    • 解释: 由于编译时 dog 被视为 Animal 类型(因为 dog 的声明类型是 Animal),它访问的是 Animal 类中的 name 字段,即 "Animal"
  2. 方法是子类的:

    • 方法是动态绑定的,也就是说,方法调用在运行时会根据对象的实际类型进行选择。
    • 虽然 dog 的引用类型是 Animal,但它指向的是一个 Dog 类型的对象。因此,当调用 dog.shout() 时,Java 会动态查找 Dog 类中是否有 shout() 方法,并执行 Dog 类中的方法实现。
    • 这就是多态的体现:即使编译时我们知道 dogAnimal 类型,实际执行时会根据对象的实际类型(Dog)来决定调用哪个方法。

总结:

  • 字段是静态绑定的,访问时取决于引用变量的类型(在你的例子中是 Animal 类型),因此即使实际对象是 Dog,字段访问的是 Animal 类中的字段。
  • 方法是动态绑定的,调用时根据实际对象的类型(在你的例子中是 Dog),因此调用的是 Dog 类中的方法。

这种机制保证了 Java 的继承结构中字段和方法行为的不同:字段继承时是“编译时绑定”的,而方法是“运行时绑定”的。

在Java中,方法调用绑定指的是将一个方法调用与方法的代码实现关联起来。Java中的绑定分为两种:静态绑定(也称为早期绑定)和动态绑定(也称为晚期绑定)。

静态绑定(Static Binding)

静态绑定发生在编译时期,它基于对象的类型进行绑定。在编译时,编译器就知道要调用哪个方法。以下情况通常会发生静态绑定:

  1. 静态方法:静态方法是属于类的,不依赖于对象,因此它们在编译时就可以确定。
  2. 私有方法:私有方法是不可被继承的,因此它们也总是在编译时绑定。
  3. final方法:final方法不能被覆盖,所以编译器在编译时就可以确定调用哪个方法。
  4. 构造方法:构造方法在对象创建时调用,它们的绑定也在编译时期确定。 静态绑定的例子:
public class Base {
    public static void staticMethod() {
        System.out.println("Static method in Base");
    }
    
    private void privateMethod() {
        System.out.println("Private method in Base");
    }
    
    public final void finalMethod() {
        System.out.println("Final method in Base");
    }
}
public class Derived extends Base {
    // 尝试覆盖静态方法、私有方法或final方法都不会影响静态绑定
}
Base obj = new Derived();
obj.staticMethod(); // 静态绑定,调用Base类的staticMethod
obj.finalMethod();  // 静态绑定,调用Base类的finalMethod

动态绑定(Dynamic Binding)

动态绑定发生在运行时期,它基于对象的实际类型进行绑定。在运行时,虚拟机(JVM)会根据对象的实际类型来决定调用哪个方法。以下情况通常会发生动态绑定:

  1. 非静态方法:当调用一个非静态方法时,JVM会根据对象的实际类型来决定调用哪个方法实现。
  2. 非私有方法:只有非私有方法可以被覆盖,因此它们是动态绑定的候选。 动态绑定的例子:
public class Base {
    public void nonStaticMethod() {
        System.out.println("Non-static method in Base");
    }
}
public class Derived extends Base {
    @Override
    public void nonStaticMethod() {
        System.out.println("Non-static method in Derived");
    }
}
Base obj = new Derived();
obj.nonStaticMethod(); // 动态绑定,调用Derived类的nonStaticMethod

在上面的例子中,尽管obj的引用类型是Base,但是实际对象是Derived类型的,因此调用nonStaticMethod时,会调用Derived类中覆盖后的方法。

总结

  • 静态绑定在编译时期确定方法调用,它依赖于变量的声明类型。
  • 动态绑定在运行时期确定方法调用,它依赖于对象的实际类型。
  • 静态绑定通常用于性能优化,因为编译器可以预先知道要调用哪个方法,而动态绑定提供了更大的灵活性,允许方法在运行时根据对象的实际类型来选择调用哪个实现。