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

Posted

技术标签:

【中文标题】重载是编译时多态性。真的吗?【英文标题】:Overloading is compile-time polymorphism. Really? 【发布时间】:2012-01-11 10:59:19 【问题描述】:

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

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("***********");
    

现在,考虑以下 sn-p:

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

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

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

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

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

【问题讨论】:

【参考方案1】:

每个 '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!");
    

使用这个greeter类会得到以下结果:

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,但它已被强制转换为 Object,因此编译器就是这样看待它的。因此,编译器可以为该调用找到最接近的匹配项是 void greetMe(Object) 方法。

【讨论】:

【参考方案2】:

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

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

即方法分派分两步进行:

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

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

【讨论】:

我知道重载的方法可以被覆盖。但是编译时多态性如何应用于重载方法呢?它们也可以被覆盖,因此必须在运行时解决。对吗? 你能再解释一下这个家庭的事情吗? Jomoos:编译器仍然可以在编译时解析选择哪些重载方法,因为参数列表不能随着覆盖而改变。在运行时,JVM 只是解析它应该从哪个对象调用该方法的这个“版本”(从编译时的重载版本中选择)。恕我外行,我没有深厚的CS背景。【参考方案3】:

什么是多态性?

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

现在,让我们将此定义应用于 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.

我希望它会清除一点。

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

我试图在我的问题中提出我的意见以获得澄清。可以参考this link.

【讨论】:

以上是关于重载是编译时多态性。真的吗?的主要内容,如果未能解决你的问题,请参考以下文章

面向对象三大特性: 多态

C++ 多态性和重载?

多态与重载

在c++中虚函数和多态性是啥意思

Java中的多态

重载与多态