为什么这个Java方法按声明的类型而不是运行时类型进行多态?
此代码:为什么这个Java方法按声明的类型而不是运行时类型进行多态?,java,polymorphism,late-binding,Java,Polymorphism,Late Binding,此代码: public class PMTest { private static class Runner { } private static class Server extends Runner { } private static class Task { public void delegate(Runner runner) { System.out.println("Task: " + ru
public class PMTest
{
private static class Runner { }
private static class Server extends Runner { }
private static class Task
{
public void delegate(Runner runner)
{
System.out.println("Task: " + runner.getClass().getName() +
" / " + this.getClass().getName());
}
}
private static class Action extends Task
{
public void delegate(Server server)
{
System.out.println("Action: " + server.getClass().getName() +
" / " + this.getClass().getName());
}
}
private static void foo(Task task, Runner runner)
{
task.delegate(runner);
}
private static void bar(Action task, Runner runner)
{
task.delegate(runner);
}
private static void baz(Action task, Server runner)
{
task.delegate(runner);
}
public static void main (String[] args)
{
try {
Server server = new Server();
Action action = new Action();
action.delegate(server);
foo(action, server);
bar(action, server);
baz(action, server);
}
catch (Throwable t) {
t.printStackTrace();
}
}
}
生成此输出:
$ java PMTest
Action: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Action: PMTest$Server / PMTest$Action
我可以非常清楚地看到,任务的方法是被选择的,而不是行动的方法。但我不明白为什么,因为对象总是知道它们是什么,而且我认为Java的后期绑定方法选择能够区分方法签名的差异。对bar()
的调用尤其令人困惑,因为此时task
被声明为Action
如果有区别的话,这就是Java 6:
$ java -version
java version "1.6.0_14"
Java(TM) SE Runtime Environment (build 1.6.0_14-b08)
BEA JRockit(R) (build R27.6.5-32_o-121899-1.6.0_14-20091001-2113-linux-ia32, compiled mode)
我可以更改代码使其工作,但我想了解为什么它不工作。谢谢你的帮助 这就是分派在java中的工作方式 分派首先基于静态参数类型,一旦选择了静态签名,则使用包含该方法的对象的运行时类型来确定使用了哪个重写 例如,在
void foo(Object o) {
if (o instanceof Number) { foo((Number) o); }
else if (o instanceof String) { foo((String) o); }
}
void foo(String s) { ... }
void foo(Number n) { ... }
{ foo((Object) "foo"); } // Calls foo(Object) which calls foo(String).
{ foo("foo"); } // Calls foo(String) without first calling foo(Object).
重载方法之间的选择总是在编译时进行,而不是在运行时。运行时多态性涉及在覆盖其他方法的方法之间进行选择,即具有相同签名的方法。因为一旦将
Server
传递到foo
或bar
,在该调用的范围内,它就不是服务器,而是运行程序
因此,当您运行delegate
时,它将根据方法的签名绑定到最合适的匹配项delegate(Runner)
不需要将作用域参数危险地向下转换到服务器中
请注意,此作用域不是在运行时完成的,它也将支持源代码的静态分析。只是你们记得服务器是一个让你们困惑的服务器。如果您分析代码时没有额外的超出范围的知识,那么您将看到委托(运行者)
确实是您唯一有效的选择。+1:这是正确的答案。总之,Java不执行双重分派(尽管可以模拟),所以分派是基于声明的参数类型,而不是运行时参数类型?我想我可以理解实际的规则,但我不明白为什么它会这样实现。看起来只是有点晚了。好吧,看起来欧内斯特回答了我的后续问题——谢谢@Joe,是的,分派基于声明的类型。原因之一是它使编译器易于优化——这就是为什么它是用C++完成的,而java的设计是C++开发人员容易学习的。人们认为应该保持这种方式的一个原因是,它使了解规则的开发人员能够很容易地了解代码的作用——具有一定级别的静态分派使代码更易于查看和学习。