重载是编译时多态性。真?

我确实知道覆盖和重载之间的语法差异。我也知道重写是运行时多态性,重载是编译时多态性。但我的问题是:“重载真的是编译时多态性吗?方法调用真的在编译时求解吗?为了澄清我的观点,让我们考虑一个示例类。

public class Greeter {
    public void greetMe() {
        System.out.println("Hello");
    }

    public void greetMe(String name) {
        System.out.println("Hello " + name);
    }

    public void wishLuck() {
        System.out.println("Good Luck");
    }
}

由于所有方法都是公共的,因此它们都可以被覆盖(包括重载的方法),对吧?例如greetMe(), greetMe(String name), wishLuck()

public class FancyGreeter extends Greeter {
    public void greetMe() {
        System.out.println("***********");
        System.out.println("*  Hello  *");
        System.out.println("***********");
    }
}

现在,请考虑以下代码段:

Greeter greeter = GreeterFactory.getRandomGreeter();
greeter.greetMe();

该方法返回一个随机对象。它可以返回 的对象,或其任何子类,如 或 或任何其他子类。将使用或动态加载类文件创建对象,并使用反射(我认为可以通过反射)或任何其他可能的方式创建对象。所有这些方法都可以在子类中被覆盖,也可能不被覆盖。因此,编译器无法知道特定方法(重载与否)是否被重写。右?另外,维基百科在虚拟函数上说:getRandomGreeter()GreeterGreeterFancyGreeterGraphicalGreetergetRandomGreeter()newGreeter

在Java中,所有非静态方法都是缺省情况下的“虚函数”。只有标有关键字 final(无法覆盖)的方法以及未继承的私有方法才是非虚拟的。

由于虚函数是在运行时使用动态方法调度解析的,并且由于所有非私有、非最终方法都是虚拟的(无论是否重载),因此必须在运行时解析它们。右?

那么,如何在编译时仍然解决重载问题呢?或者,我有什么误解,还是我错过了什么?


答案 1

每个“迎宾员”类都有 3 个虚拟方法:、 和 。void greetMe()void greetMe(String)void wishLuck()

当您调用编译器时,可以确定应该从方法签名调用三个虚拟方法中的哪一个 - 即。一个,因为它不接受任何参数。调用该方法的特定实现取决于实例的类型,并在运行时进行解析。greeter.greetMe()void greetMe()void greetMe()greeter

在您的示例中,编译器确定要调用哪个方法是微不足道的,因为方法签名都是完全不同的。显示“编译时多态性”概念的一个稍微好一点的示例可能如下所示:

class Greeter {
    public void greetMe(Object obj) {
        System.out.println("Hello Object!");
    }

    public void greetMe(String str) {
        System.out.println("Hello String!");
    }
}

使用此欢迎程序类将给出以下结果:

Object obj = new Object();
String str = "blah";
Object strAsObj = str;

greeter.greetMe(obj); // prints "Hello Object!"
greeter.greetMe(str); // prints "Hello String!"
greeter.greetMe(strAsObj); // prints "Hello Object!"

编译器将使用编译时类型挑选出具有最特定匹配的方法,这就是第二个示例工作并调用该方法的原因。void greetMe(String)

最后一个调用是最有趣的一个:尽管 strAsObj 的运行时类型是 ,但它已被强制转换为 一个,所以这就是编译器对它的看法。因此,编译器可以为该调用找到的最接近的匹配项是方法。StringObjectvoid greetMe(Object)


答案 2

重载的方法仍然可以被重写,如果这是你所要求的。

重载方法就像不同的系列,即使它们共享相同的名称。编译器静态地选择给定签名的一个系列,然后在运行时将其调度到类层次结构中最具体的方法。

也就是说,方法分派分两步执行:

  • 第一个是在编译时使用可用的静态信息完成的,编译器将为签名发出一个签名,该签名与调用方法的对象的声明类型中的重载方法列表中的当前方法参数最匹配。call
  • 第二步是在运行时执行的,给定应该调用的方法签名(上一步,还记得吗?),JVM会将其分派到接收对象的实际类型中最具体的重写版本。

如果方法参数类型根本不是协变的,则重载等效于在编译时修改方法名称;因为它们实际上是不同的方法,所以JVM永远不会根据接收器的类型互换地调度它们。