为什么说重写是运行时多态?

为什么说重写是运行时多态?



重写(Overriding)被称为运行时多态(Runtime Polymorphism),是因为它涉及到在程序运行时(而不是编译时)根据对象的实际类型来确定要调用的方法。在运行时,Java虚拟机会动态地决定要调用哪个方法实现,这取决于对象的实际类型,而不仅仅是编译时的类型。
以下是为什么重写被称为运行时多态的一些原因:
  1. 动态绑定:在重写中,方法的选择是在运行时(动态)而不是编译时(静态)确定的。编译器不知道对象的实际类型,只有在运行时才能够确定。
  2. 方法覆盖:重写是通过子类提供的方法来覆盖父类中的方法。子类可以提供不同的方法实现,但方法签名必须与父类中的方法相匹配。
  3. 多态性:重写是多态性的一种表现形式。多态性是指同一个方法调用可以根据对象的实际类型而有不同的行为。当使用父类的引用变量来引用子类对象时,调用的方法将根据子类的类型而变化,这就是多态性。
举例来说,考虑以下的Java代码:
class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 父类引用指向子类对象
        animal.makeSound(); // 运行时多态,调用子类的方法
    }
}
在这个示例中,Dog 类继承自 Animal 类并重写了 makeSound 方法。在 main 方法中,父类引用 animal 指向子类对象 Dog。当调用 animal.makeSound() 时,实际上调用的是 Dog 类中的 makeSound 方法,这是由运行时对象的类型决定的。这就是运行时多态性的体现,方法调用的实际行为在运行时动态确定。

为什么说“编译器不知道对象的实际类型,只有在运行时才能够确定”?



这句话的关键在于理解编译时和运行时两个不同的阶段以及编译器和运行时环境的职责。
  1. 编译时(Compile Time):编译时是指当您编写代码并使用编译器来编译源代码时的阶段。在编译时,编译器会检查语法错误、类型检查、生成中间代码等。编译器的主要任务是将高级编程语言的代码翻译成可执行代码或中间代码,但它不会执行代码。
  2. 运行时(Runtime):运行时是指在计算机上执行已编译的程序或代码的阶段。在运行时,程序被加载到内存中,并由计算机的处理器执行。这时,程序开始真正运行并产生实际的结果。
编译器的职责是在编译时检查代码的类型和语法,但编译器不了解程序在运行时会接收什么样的数据或对象。这就是为什么说编译器不知道对象的实际类型的原因。编译器只能根据代码中的类型信息来执行静态类型检查,但不能了解程序在运行时会使用哪些具体对象。
运行时环境(如Java虚拟机)负责实际的程序执行。在运行时,程序会创建对象并处理数据,而这些对象和数据的实际类型是在运行时动态确定的。这就是说,编译器只能在编译时检查代码,但在运行时才能真正了解对象的实际类型。
在多态性的情况下,编译器只能知道变量的静态类型(声明时的类型),但在运行时,实际调用的方法是基于对象的实际类型决定的。这就是为什么说多态性是在运行时实现的,编译器无法提前知道对象的实际类型。

可是代码中Animal animal = new Dog();不是已经明确指出对象是Dog类型了吗?



您提到的代码Animal animal = new Dog();确实在编译时指定了变量animal的静态类型为Animal,但在运行时,这个变量引用的对象实际上是Dog类型的。这就是多态性的一个示例,其中变量的静态类型和实际类型不一致。
在编译时,编译器只能知道变量animal的静态类型是Animal,因此它将根据Animal类中的方法列表来验证对animal对象的方法调用。但在运行时,实际调用的方法会基于animal引用的对象的实际类型,这就是动态绑定(或运行时多态性)的体现。
在运行时,当执行animal.makeSound()时,由于animal引用的对象是Dog类型的,因此会调用Dog类中的makeSound方法,而不是Animal类中的版本。这种行为是在运行时确定的,因为编译器不知道animal引用的对象实际上是什么类型,直到运行时才能够确定。
因此,虽然编译时指定了变量的静态类型,但实际的方法调用是在运行时基于对象的实际类型确定的,这就是为什么称之为运行时多态性。

animal变量引用的类型不是已经明确写出是Dog类型了吗,编译的时候为什么不知道?



对于这个具体的代码示例Animal animal = new Dog();,您在代码中确实明确写出了animal引用的类型是Dog,因此在这个示例中,编译器是知道animal的实际类型是Dog的。
我的之前的回答可能没有清晰地表达,我为此道歉。在这个特定的示例中,编译器确实可以知道animal引用的实际类型是Dog。所以,在这个特定的上下文中,编译器知道对象的实际类型。
在我的前一个回答中,我试图解释为什么在某些情况下,编译器无法确定对象的实际类型,这通常涉及到更复杂的多态性场景。但在您提供的示例中,编译器确实可以知道对象的实际类型。我希望这次的回答更加明确。感谢您的指正。

那什么情况下编译器不会知道对象的实际类型呢?



编译器在某些情况下无法确定对象的实际类型,通常涉及到多态性(Polymorphism)的情况。以下是一些情况:
  1. 方法参数和返回值:当方法参数或返回值的类型是父类或接口类型时,编译器只能知道参数或返回值的静态类型,无法确定实际传递或返回的对象类型。例如:
    public void processAnimal(Animal animal) {
        // 编译器知道animal的静态类型是Animal,但不知道实际传递的是哪个子类的对象
    }
    
  2. 集合和泛型:当使用泛型集合时,编译器可能无法确定集合中的元素类型。例如:
    List<Animal> animals = new ArrayList<>();
    animals.add(new Dog());
    
    虽然编译器知道animals是一个List,但它无法确切知道列表中的元素是什么类型。
  3. 反射:通过反射机制,可以在运行时加载和创建类,编译器无法在编译时确定类的类型。例如:

    Class<?> clazz = Class.forName("com.example.MyClass");
    Object instance = clazz.newInstance();
    
    在这个示例中,编译器无法知道clazz的确切类型。
总之,编译器通常可以在明确的静态类型情况下知道对象的实际类型,但在某些多态性场景下,编译器无法在编译时确定对象的实际类型,只能在运行时根据对象的实际类型来确定行为。这就是多态性的基本概念。

你可能感兴趣的:(Java八股文,java,开发语言)