为什么这个 Java 方法通过声明类型而不是运行时类型进行多态?
此代码:
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)
我可以更改我的代码以使其工作,但我想了解为什么它不起作用。感谢您的帮助!
This code:
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();
}
}
}
produces this output:
$ java PMTest
Action: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Task: PMTest$Server / PMTest$Action
Action: PMTest$Server / PMTest$Action
I can see very clearly that Task's method is being chosen over Action's method. I don't understand why, though, since the objects always know what they are and I thought Java's late-binding method selection would be able to distinguish the difference in method signatures. The call to bar()
is especially confusing, as task
is declared as an Action
at that point.
If it makes a difference, this is 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)
I can change my code to make it work, but I'd like to understand why it doesn't work. Thanks for the help!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这就是 java 中调度的工作原理。
调度首先基于静态参数类型,一旦选择了静态签名,则包含该方法的对象的运行时类型将用于确定使用哪个重写。
例如,在
That's how dispatch works in java.
Dispatch is based first on static parameter types, and once a static signature has been chosen, then the runtime type of the object containing the method is used to figure out which overriding is used.
For example, in
重载方法之间的选择总是在编译时进行,而不是在运行时进行。运行时多态性涉及在重写其他方法的方法之间进行选择——即具有相同签名的方法。
The choice between overloaded methods is always made at compile time, never at runtime. Runtime polymorphism involves choosing between methods that override other methods -- i.e., methods with identical signatures.
因为一旦将
Server
传递到foo
或bar
中,在该调用的范围内它就不是Server
而是而是一个Runner
。因此,当您运行
delegate
时,它将根据方法的签名绑定到最合适的匹配项。delegate(Runner)
不需要将作用域参数危险地向下转换为Server
。请注意,此范围界定不是在运行时完成的,它也适用于源代码的静态分析。只是您记得服务器是一个
Server
,这让您感到困惑。如果您在没有额外的超出范围的知识的情况下分析代码,那么您会发现delegate(Runner)
确实是您唯一有效的选择。Because once you pass
Server
intofoo
orbar
, in the scope of that call it is not aServer
but rather aRunner
.Therefore, when you run
delegate
it will bind to the most appropriate match, according to the method's signature.delegate(Runner)
doesn't require the dangerous downcast of the scoped parameter into aServer
.Note that this scoping is not done at runtime, it will hold up to static analysis of the source code too. It's just that you remember server was a
Server
that's confusing you. If you analyse the code without that extra out-of-scope knowledge, then you'll see thatdelegate(Runner)
really is you only valid choice.