Java方法调度如何与generics和抽象类一起使用?

我遇到了今天Java没有调用我预期的方法的情况 – 这是最小的测试用例:(对不起,这似乎是人为的 – ‘现实世界’场景要复杂得多,而且更有意义从“为什么你会这样做?”的立场。)

我特别感兴趣的是为什么会这样,我不关心重新设计的建议。 我有一种感觉,这是在Java Puzzlers,但我没有我的副本方便。

请参阅以下Test .getValue()中的具体问题:

public class Ol2 { public static void main(String[] args) { Test t = new Test() { protected Integer value() { return 5; } }; System.out.println(t.getValue()); } } abstract class Test { protected abstract T value(); public String getValue() { // Why does this always invoke makeString(Object)? // The type of value() is available at compile-time. return Util.makeString(value()); } } class Util { public static String makeString(Integer i){ return "int: "+i; } public static String makeString(Object o){ return "obj: "+o; } } 

此代码的输出是:

 obj: 5 

不,编译时无法使用值类型。 请记住,javac只会编译一个代码副本,用于所有可能的T代码。 鉴于此,编译器在getValue()方法中使用的唯一可能类型是Object。

C ++是不同的,因为它最终会根据需要创建代码的多个编译版本。

因为关于makeString()使用的决定是在编译时做出的,并且基于T可以是任何东西的事实,必须是Object版本。 想一想。 如果您执行了Test则必须调用Object版本。 因此, Test所有实例都将使用makeString(Object)

现在,如果你做了类似的事情:

 public abstract class Test { ... } 

事情可能会有所不同。

Josh Bloch的Effective Java有一个很好的讨论,澄清了出现的混乱,因为dispatch对于重载vs overridden(在子类中)方法的工作方式不同。 重载方法中的选择—这个问题的主题—在编译时确定; 重载方法中的选择是在运行时完成的(因此可以了解对象的特定类型。)

这本书比我的评论更清楚:参见“第41项:明智地使用重载”