24

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

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()方法返回一个随机Greeter对象。它可以返回 的对象Greeter或其任何子类,如FancyGreeterGraphicalGreeter或任何其他子类。将getRandomGreeter()使用new或动态加载类文件创建对象,并使用反射(我认为反射是可能的)或任何其他可能的方式创建对象。所有这些方法Greeter可能会或可能不会在子类中被覆盖。所以编译器无法知道某个特定的方法(重载与否)是否被覆盖。对?此外,维基百科在Virtual functions上说:

在 Java 中,所有非静态方法默认都是“虚拟函数”。只有用关键字 final 标记的不能被覆盖的方法,以及不能被继承的私有方法是非虚拟的。

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

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

4

5 回答 5

15

每个 'Greeter' 类都有 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 的运行时类型是String,但它已被强制转换为 an ,Object因此编译器就是这样看待它的。因此,编译器可以为该调用找到最接近的匹配是void greetMe(Object)方法。

于 2011-12-02T12:37:06.513 回答
13

重载的方法仍然可以被覆盖,如果这是您所要求的。

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

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

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

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

于 2011-12-02T11:54:54.447 回答
10

什么是多态性?

会计。对我来说如果一个实体可以用一种以上的形式表示,则称该实体表现出多态性。

现在,让我们将此定义应用于 Java 构造:

1)运算符重载是编译时多态性。

例如,+运算符可用于将两个数字相加或连接两个字符串。这是一个多态性的例子,严格来说是编译时多态性。

2)方法重载是编译时多态性。

例如,同名的方法可以有多个实现。它也是一种编译时多态性。

It's compile-time because before execution of program compiler decides the flow of program i.e which form will be used during run-time.

3) 方法覆盖是运行时多态性。

例如,具有相同签名的方法可以有多个实现。这是一个运行时多态性。

4) 使用基类代替派生类是运行时多态性。

例如,interface引用可以指向它的任何实现者。

It's run-time because the flow of program can't be known before execution i.e. only during run-time it can be decided that which form will be used.

我希望它清除一点。

于 2011-12-02T12:26:53.773 回答
3

在这方面重载意味着函数的类型是在编译时静态确定的,而不是动态分派。

幕后真正发生的是,对于名为“foo”的方法,类型为“A”和“B”,创建了两个方法(“foo_A”和“foo_B”)。它们中的哪一个被调用是在编译时确定的(foo((A) object)foo((B) object)导致foo_A被调用或foo_B)。所以在某种程度上这编译时多态性,尽管真正的方法(即在类层次结构中采用哪个实现)是在运行时确定的。

于 2011-12-02T12:04:36.027 回答
0

我强烈反对将方法重载称为编译时多态性
我同意方法重载是静态绑定(编译时),但我没有看到多态性。

我试图在我的问题中提出我的意见以获得澄清。你可以参考这个链接。

于 2019-08-30T22:35:15.953 回答