8

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

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

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. 从编译器的角度来看,Smartphone 中 switchon 方法的起源是什么?它是从基类 Gadget 继承的吗?或者它是 switchonable 接口要求的 switchon 方法的实现?注释在这里有什么不同吗?

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

我理解错了什么?为什么运行时在第一个 for 循环中解析子类,但在第二个 for 循环中不解析?

最后,一个关于Java运行时/编译时多态性的清晰教程的链接将不胜感激。(请不要张贴 Java 教程跟踪链接,在深入讨论更细微的差别时,我没有发现这些材料特别令人印象深刻)。

4

8 回答 8

4

这就是它的工作方式:
编译时间

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

运行

  • 在执行期间,JVM 开始寻找具有在编译时 精确定义的签名的候选方法。
  • 对可执行方法的搜索实际上是从真正的 Object 实现 Class(可以是 type-Class 的子类)开始,并向上浏览整个层次结构。

您的列表是使用 Gadget 类型定义的。

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

当您要求gadget.switchon();编译器将switchon()在 Gadget 类中查找该方法时,候选签名被简单地确认为switchon().

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

这是第二个 for 循环中发生的情况

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

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

希望能帮助到你!

于 2013-07-13T22:20:06.573 回答
1

From the compilers point of view, what is the origin of the switchon method in Smartphone? Is it inherited from the base class Gadget? Or is it an implementation of the switchon method mandated by the switchonable interface?

The second case

Does the annotation make any difference here?

Not at all, @Override is just a helper, whe you use it you are telling the compiler: "my intention is to override the method from a supertype, please throw an exception and don't compile this if it is not overriding anything"

About the second question, in this case the method that better match acording to its signature is the one to be called. At run time in the second loop your objects have the supertype "associated", that's the reason public void demo(Gadget g) will be called rather than public void demo(Smartphone g)

于 2013-07-13T22:04:28.273 回答
0

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

但是运行时多态性仅适用于方法的“this”指针,而不适用于参数,因此编译器对方法签名的选择将在第二种情况下“规则”。

于 2013-07-13T22:20:39.720 回答
0

1.It shouldn't matter. Because it is extending Gadget, if you don't override and call switchon() from a smartphone, it would say "Gadget is switching on!". When you have both an interface and a parent class with the same method, it really doesn't matter.

2.The first loop works and the second doesn't because of the way java looks at objects. When you call a method from an object, it takes the method directly from that object, and thus knows whether smartphone or gadget. When you send either a Smartphone or Gadget into an overloaded method, everything in that class is called a Gadget, whether it is actually a smartphone or not. Because of this, it uses the gadget method. To make this work, you would want to use this in the demo(Gadget g) method of DemoPersonnel:

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

Sorry I don't have a link to a tutorial, I learned through a combination of AP Computer Science and experience.

于 2013-07-13T22:03:21.180 回答
0
  1. 注释在这里没有任何区别。从技术上讲,就像你在做两件事:一次性覆盖父 switchon() 和实现 switchon() 接口方法。

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

希望这可以帮助。

于 2013-07-13T22:10:01.517 回答
0

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

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

于 2013-07-13T22:10:24.187 回答
0
  1. 对于编译器,Smartphone 从 Gadget 继承 switchon() 方法“实现”,然后 Smartphone 用自己的实现覆盖继承的实现。另一方面,可切换接口要求 Smartphone 提供 switchon() 方法定义的实现,并由 Smartphone 中重写的实现来实现。

  2. 第一种情况按您的预期工作,因为它确实是多态的情况,即您有一个合同和两个实现——一个在小工具中,另一个在智能手机中;后来“覆盖”了以前的实现。第二种情况“不应该”像您期望的那样工作,因为只有一份合同和一份实施。请注意,您“没有覆盖” demo() 方法,实际上是“重载”了 demo() 方法。而且,重载意味着两个“不同”的唯一方法定义,它们只共享“相同的名称”。因此,当您使用 Gadget 参数调用 demo() 时,这是一个合约和一个实现的情况,因为编译器会将方法名称与确切的方法参数类型匹配,这样做会调用“不同的方法”

于 2013-07-13T22:11:34.243 回答
0

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

这是 java 语言规范的链接

正如它所说:

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

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

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

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

于 2013-07-13T22:16:33.217 回答