1. 静态绑定与动态绑定概述

多态(Polymorphism)是面向对象编程的核心特性之一,它允许同一个方法调用在不同对象上表现出不同的行为。当一个方法表现出多态性时,编译器或运行时环境需要决定具体调用哪个实现。

  • 静态绑定(Static Binding):在 编译期 就确定方法调用的具体实现,也叫“早期绑定”。
  • 动态绑定(Dynamic Binding):在 运行时 才决定调用哪个方法,也叫“晚期绑定”。

理解这两者的区别,能帮你避免一些看似“反直觉”的踩坑问题,尤其是在继承、重载、重写混用时。


2. 通过代码理解绑定机制

我们从一个经典的继承场景入手:Dog 继承自 Animal,并重写了父类的方法。

2.1 基类 Animal

public class Animal {

    static Logger logger = LoggerFactory.getLogger(Animal.class);

    public void makeNoise() {
        logger.info("generic animal noise");
    }

    public void makeNoise(Integer repetitions) {
        while(repetitions != 0) {
            logger.info("generic animal noise countdown " + repetitions);
            repetitions -= 1;
        }
    }
}

2.2 子类 Dog

public class Dog extends Animal {

    static Logger logger = LoggerFactory.getLogger(Dog.class);
    
    @Override
    public void makeNoise() {
        logger.info("woof woof!");
    }
}

注意:makeNoise() 方法被 @Override 修饰,表示这是对父类方法的 重写(Override),而不是重载(Overload)。


2.3 测试代码与输出分析

我们写一段测试代码来观察行为:

Animal animal = new Animal();
animal.makeNoise();           // 输出:generic animal noise
animal.makeNoise(3);          // 输出:倒数3次 noise

Animal dogAnimal = new Dog(); // 向上转型
dogAnimal.makeNoise();        // 输出:woof woof!

输出结果:

com.baeldung.binding.Animal - generic animal noise 
com.baeldung.binding.Animal - generic animal noise countdown 3
com.baeldung.binding.Animal - generic animal noise countdown 2
com.baeldung.binding.Animal - generic animal noise countdown 1
com.baeldung.binding.Dog - woof woof!

关键点来了:

  • dogAnimalAnimal 类型的引用,但指向的是 Dog 实例。
  • ✅ 调用 makeNoise() 时,实际执行的是 Dog 类中的版本。
  • ⚠️ 这说明:方法重写(Override)触发了动态绑定 —— 具体调用哪个方法,由运行时对象的实际类型决定。

2.4 静态方法的绑定:永远是静态绑定

我们再加一个工具类,演示静态方法的行为:

class AnimalActivity {

    public static void eat(Animal animal) {
        System.out.println("Animal is eating");
    }

    public static void eat(Dog dog) {
        System.out.println("Dog is eating");
    }
}

然后调用:

Animal dogAnimal = new Dog();
AnimalActivity.eat(dogAnimal);

输出是:

Animal is eating

即使 dogAnimal 实际指向的是 Dog 对象,但静态方法的重载选择是 基于引用类型(这里是 Animal),而不是实际对象类型。

结论:静态方法(static)使用 静态绑定,在编译期就决定了调用哪个版本。

🔍 补充:静态方法不能被重写(Override),子类定义同名方法只是“隐藏”父类方法(hide),不构成多态。


2.5 哪些方法使用静态绑定?

以下情况,Java 会进行 静态绑定

  • static 方法:绑定到类,而非实例
  • private 方法:无法被继承,自然无法多态
  • final 方法:禁止重写,编译器可直接确定实现
  • ✅ 构造方法(constructor):不能被重写,也是静态绑定
  • ✅ 方法重载(Overload):参数类型不同,编译期即可确定调用哪个版本

哪些方法使用动态绑定?

  • ✅ 普通的实例方法(非 private/final/static),只要可能被重写,就是动态绑定
  • ✅ Java 中几乎所有非静态的、可被重写的方法,默认都是 虚方法(virtual method)
  • ✅ JVM 通常通过 虚方法表(vtable) 实现动态分派,类似 C++ 的机制

💡 小知识:Java 中没有 virtual 关键字,因为 所有非 final、非 static、非 private 的实例方法默认就是虚方法


3. 总结

特性 静态绑定 动态绑定
发生时机 编译期 运行时
触发场景 重载、static、final、private 方法 方法重写(Override)
性能 略快(无需查表) 稍慢(需查虚方法表)
灵活性 高(支持多态)

关键结论

  • 方法重载(Overload) → 静态绑定
  • 方法重写(Override) → 动态绑定
  • 静态、私有、final 方法 → 静态绑定
  • 普通实例方法 → 动态绑定

理解这一点,能帮你更好设计继承体系,避免误用重载/重写,也能在调试时快速定位“为什么调的不是我想的那个方法”。

📁 示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-lang-oop-others


原始标题:Static and Dynamic Binding in Java