class A { void F() { System.out.println("a"); }}
class B extends A { void F() { System.out.println("b"); }}
public class X {
public static void main(String[] args) {
A objA = new B();
objA.F();
}
}
这里,F()
是动态调用的,不是吗?
... Java字节码不支持 动态方法调用。有 三种支持的调用模式: invokestatic,invokespecial, invokeinterface或invokevirtual。 这些模式允许调用方法 已知签名。我们谈论 强类型语言。这允许 直接进行一些检查 编译时间。
另一方面,动态 语言使用动态类型。所以我们可以 在编译时调用一个未知的方法 时间,但那是完全不可能的 使用Java字节码。
我缺少什么?
答案 0 :(得分:13)
您对动态绑定的动态调用感到困惑。
第一个允许类型检查器接受程序,在这些程序中,您不确定在运行时是否在对象上存在方法,而动态绑定只是根据对象的运行时类型选择正确的实现< strong>但保持静态类型检查。
这是什么意思?
这意味着在您的示例中,Java将调用对象B
上的实现,因为objA
变量的运行时类型为B
;它会编译,因为它知道B
是 A
所以方法调用不会在运行时失败(objA
会有{{1肯定的实现。)
使用动态调用,它不会在编译时检查您调用的对象的类型F
是否包含该方法,当然如果在执行期间该方法不会引发异常在指定的对象上可用。
只是为了琐事:F
功能将添加到Java7中,因为许多脚本语言已编写为在JVM之上工作,缺乏动态调用功能迫使这些语言的开发人员添加中间脚本和真正的JVM之间的层,它关注使用反射的动态调用。当然这种方法会带来很多开销(想想Grovvy的invokedynamic
),这就是为什么Sun决定给他们一个帮助..
答案 1 :(得分:1)
在您的示例中,调用了正确的方法,因为多态地,B的实例看起来像A的实例。可以通过检查对象的运行时类型来定位该方法;就是B;与对象引用的编译时类型相反,A。另一个重要的部分是方法的签名 - 这些必须始终匹配(当然是多态的)。
这与动态语言不同,因为那些对象基本上没有编译时间 - 所有内容都必须在运行时解析。
答案 2 :(得分:1)
事实上,你所缺少的是这是“invokevirtual”的一部分,文章对此进行了解释。
您只是覆盖该方法,并使用虚方法表来调用正确的方法。
答案 3 :(得分:0)
我不会将您的示例称为“ dynamic ”,而不是 virtual 。因为在编译时方法名称和签名是已知的(并且它的存在由编译器检查)。唯一在运行时解决的是用于该方法的具体实现。
“动态”方法调用的更恰当的示例将涉及反射,(请参阅Method类)。这样,在编译类型中存在未知的方法可以在运行时调用(这被框架广泛使用,而不是应用程序代码)。
你提到的这篇文章在这方面似乎有点误导。但是,你明确调用的方法的签名必须在编译时知道/检查,因此,从这个意义上说,Java不是动态的。
答案 4 :(得分:0)
您可以制作功能界面。
class Logger {
private BiConsumer<Object, Integer> logger = null;
// ...
private Logger(Object logger) {
this.akkaLogger = (LoggingAdapter) logger;
this.logger = (message, level) -> {
switch (level) {
case INFO: akkaInfo(message);
break;
case DEBUG: akkaDebug(message);
break;
case ERROR: akkaError(message);
break;
case WARN: akkaWarn(message);
break;
}
};
}
private Logger() {
this.logger = (message, level) -> System.out.println(message);
}
// ...
}