考虑这个代码(完整的类,运行正常,为了简洁起见,在一个类中的所有类)。
我的问题在代码清单之后:
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);
}
}
}
问题:
从编译器的角度来看,智能手机中switchon方法的起源是什么?它是从基类Gadget继承的吗?或者它是可切换接口强制要求的switchon方法的实现?注释在这里有什么不同吗?
在main方法中,第一个循环:这里,我们看到一个运行时多态的情况 - 即,当第一个for循环运行,并且调用gadget.switchon()时,它首先打印“Gadget正在切换在“,然后它打印”智能手机正在打开“。但是在第二个循环中,这个运行时分辨率不会发生,两次调用demo的输出都是“Demoing a gadget”,而我期望它在第一次迭代时打印“Demoing a gadget”,并且“Demoing a smartphone”第二次。
我理解错误是什么?为什么运行时在第一个for循环中解析子类,但在第二个for循环中不这样做?
最后,我们将非常感谢Java中有关运行时/编译时多态的清晰教程的链接。 (请不要发布Java教程跟踪链接,在讨论可观深度的细微差别时,我没有发现这些材料特别令人印象深刻)。
答案 0 :(得分:4)
这很快就会起作用:
编制时间
运行时
您的列表是使用类型小工具定义的。
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)
注释在这里没有任何区别。从技术上讲,就像你正在做两件事:重写父switchon()并一次性实现switchon()接口方法。
方法查找(关于方法参数)不是动态地(在运行时)完成的,而是在编译时静态完成的。看起来很奇怪,但这就是它的运作方式。
希望这有帮助。
答案 4 :(得分:0)
首先回答问题2:在第二个循环中传递一个类型为Gadget的对象因此,演示类中的最佳匹配是获取小工具的方法。这是在编译时解决的。
问题1:注释没有区别。它只是表明你在接口中覆盖(实现)方法。
答案 5 :(得分:0)
对于编译器,Smartphone从Gadget继承switchon()方法“implementation”,然后Smartphone用自己的实现覆盖继承的实现。另一方面,可切换接口指示智能手机提供switchon()方法定义的实现,并且通过智能手机中覆盖的实现来实现。
第一种情况正如您所期望的那样,因为它确实是多态的情况,即您有一个合同和两个实现 - 一个在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,所以编译器选择的方法签名将在第二种情况下“规则”。