混淆Java中的多态性

时间:2013-07-13 21:48:03

标签: java polymorphism

考虑这个代码(完整的类,运行正常,为了简洁起见,在一个类中的所有类)。

我的问题在代码清单之后:

import java.util.LinkedList;
import java.util.List;

class Gadget {
    public void switchon() {
        System.out.println("Gadget is Switching on!");
    }
}

interface switchonable {
    void switchon();
}

class Smartphone extends Gadget implements switchonable {
    @Override
    public void switchon() {
        System.out.println("Smartphone is switching on!");
    }
}

class DemoPersonnel {
    public void demo(Gadget g) {
        System.out.println("Demoing a gadget");
    }

    public void demo(Smartphone s) {
        System.out.println("Demoing a smartphone");
    }
}

public class DT {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<Gadget> l = new LinkedList<Gadget>();
        l.add(new Gadget());
        l.add(new Smartphone());
        for (Gadget gadget : l) {
            gadget.switchon();
        }

        DemoPersonnel p = new DemoPersonnel();
        for (Gadget gadget : l) {
            p.demo(gadget);
        }
    }
}

问题:

  1. 从编译器的角度来看,智能手机中switchon方法的起源是什么?它是从基类Gadget继承的吗?或者它是可切换接口强制要求的switchon方法的实现?注释在这里有什么不同吗?

  2. 在main方法中,第一个循环:这里,我们看到一个运行时多态的情况 - 即,当第一个for循环运行,并且调用gadget.switchon()时,它首先打印“Gadget正在切换在“,然后它打印”智能手机正在打开“。但是在第二个循环中,这个运行时分辨率不会发生,两次调用demo的输出都是“Demoing a gadget”,而我期望它在第一次迭代时打印“Demoing a gadget”,并且“Demoing a smartphone”第二次。

  3. 我理解错误是什么?为什么运行时在第一个for循环中解析子类,但在第二个for循环中不这样做?

    最后,我们将非常感谢Java中有关运行时/编译时多态的清晰教程的链接。 (请不要发布Java教程跟踪链接,在讨论可观深度的细微差别时,我没有发现这些材料特别令人印象深刻)。

8 个答案:

答案 0 :(得分:4)

这很快就会起作用:
编制时间

  • 编译器为所请求的方法定义所需的签名
  • 一旦定义了签名,编译器就会开始在类型类
  • 中查找它
  • 如果找到任何具有所需签名的兼容候选方法,则返回错误

运行时

  • 在执行期间,JVM开始在编译时定义签名为 完全 的候选方法。
  • 对可执行方法的搜索实际上是从真正的Object实现类(可以是类型类的子类)开始,并浏览整个层次结构。

您的列表是使用类型小工具定义的。

for (Gadget gadget : l) {
        gadget.switchon();
    }

当您要求gadget.switchon();时,编译器将在Gadget类中查找switchon()方法,并且就像那里一样,候选签名只是确认为switchon()

在执行期间,JVM将从智能手机类中查找switchon()方法,这就是它显示正确消息的原因。

以下是第二个for-loop中发生的事情

DemoPersonnel p = new DemoPersonnel();
    for (Gadget gadget : l) {
        p.demo(gadget);
    }

这种情况下的签名适用于两个对象demo(Gadget g),这就是为什么对于两个迭代都执行方法demo(Gadget g)

希望它有所帮助!

答案 1 :(得分:1)

  

从编译器的角度来看,智能手机中switchon方法的起源是什么?它是从基类Gadget继承的吗?或者它是由可切换接口强制执行的switchon方法吗?

第二种情况

  

注释在这里有什么不同吗?

完全没有, @Override 只是一个帮助,当你使用它时,你告诉编译器:“我的意图是从超类型覆盖方法,请抛出异常并且不要”如果没有覆盖任何内容,请编译这个“

关于第二个问题,在这种情况下,根据其签名更好地匹配的方法是要调用的方法。在第二个循环中的运行时,您的对象具有“关联”超类型,这就是调用 public void demo(Gadget g)而不是 public void demo(Smartphone g)的原因

答案 2 :(得分:0)

1.无所谓。因为它正在扩展小工具,如果你没有覆盖并从智能手机调用switchon(),它会说“Gadget正在开启!”。如果你有一个接口和一个具有相同方法的父类,那真的没关系。

2.第一个循环有效,第二个循环没有,因为java查看对象的方式。当您从对象调用方法时,它直接从该对象获取方法,从而知道智能手机或小工具。当您将智能手机或小工具发送到重载方法时,该类中的所有内容都称为小工具,无论它是否实际上是智能手机。因此,它使用gadget方法。为了使这项工作,您可以在DemoPersonnel的演示(Gadget g)方法中使用它:

if(gadget instanceof Smartphone){
    System.out.println("Demoing a gadget");
}else{
    System.out.println("Demoing a smartphone");
} 

抱歉,我没有指向教程的链接,我通过AP计算机科学和经验的结合学到了。

答案 3 :(得分:0)

  1. 注释在这里没有任何区别。从技术上讲,就像你正在做两件事:重写父switchon()并一次性实现switchon()接口方法。

  2. 方法查找(关于方法参数)不是动态地(在运行时)完成的,而是在编译时静态完成的。看起来很奇怪,但这就是它的运作方式。

  3. 希望这有帮助。

答案 4 :(得分:0)

首先回答问题2:在第二个循环中传递一个类型为Gadget的对象因此,演示类中的最佳匹配是获取小工具的方法。这是在编译时解决的。

问题1:注释没有区别。它只是表明你在接口中覆盖(实现)方法。

答案 5 :(得分:0)

  1. 对于编译器,Smartphone从Gadget继承switchon()方法“implementation”,然后Smartphone用自己的实现覆盖继承的实现。另一方面,可切换接口指示智能手机提供switchon()方法定义的实现,并且通过智能手机中覆盖的实现来实现。

  2. 第一种情况正如您所期望的那样,因为它确实是多态的情况,即您有一个合同和两个实现 - 一个在Gadget中,另一个在Smartphone中;后来“覆盖”了以前的实现。第二种情况“不应该”按预期工作,因为只有一个合同和一个实现。请注意,您“没有覆盖”demo()方法,实际上是在“重载”demo()方法。并且,重载意味着两个“不同”的唯一方法定义,它们只共享“相同的名称”。因此,当您使用Gadget参数调用demo()时,这是一个契约和一个实现的情况,因为编译器将使用精确的方法参数类型匹配方法名称,并且通过这样做将在两者中调用“不同的方法”循环的迭代。

答案 6 :(得分:0)

关于第二个问题:  在Java中,动态方法分派仅针对调用方法的对象发生,而不是针对重载方法的参数类型。

Here is a link to the java language specification

正如它所说:

  

调用方法时(第15.12节),实际参数的数量(和   任何显式类型参数)和编译时类型   在编译时使用参数来确定签名   将被调用的方法(§15.12.2)。如果方法是   invoked是一个实例方法,要调用的实际方法是   使用动态方法查找(第15.12.4节)在运行时确定。

基本上:方法参数的编译时类型用于确定要调用的方法的签名

在运行时,调用该方法的对象的类确定调用该方法的哪个实现,同时考虑到它可能是覆盖该方法的声明类型的子类的实例。

在你通过new child()创建子类的对象的情况下;并将其传递给重载方法,它具有关联的超类类型。因此调用带有父对象的重载方法。

答案 7 :(得分:0)

编译器根据方法的声明类型和声明的参数类型选择方法签名。因此,switchon收到Gadget的“this”指针,这是编译器将在其生成的代码中引用的方法的版本。当然,运行时多态可以改变它。

但是运行时多态只适用于方法的“this”指针,而不是parms,所以编译器选择的方法签名将在第二种情况下“规则”。