2009-01-17 43 views
7

我今天遇到了一个情况,那就是Java没有调用我期望的方法 - 这是最简单的测试用例:(我很抱歉,这看起来很有意思 - “真实世界”的场景要复杂得多, “你为什么会这么做?”的立场。)Java方法调度如何与泛型和抽象类一起工作?

我特别感兴趣的是为什么会发生这种情况,我不在乎重新设计的建议。我有一种感觉,这是在Java Puzzlers中,但我没有我的副本。

参见下面的试验<Ť> .getValue()内赞扬具体问题:

public class Ol2 { 

    public static void main(String[] args) { 
     Test<Integer> t = new Test<Integer>() { 
      protected Integer value() { return 5; } 
     }; 

     System.out.println(t.getValue()); 
    } 
} 


abstract class Test<T> { 
    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 

回答

6

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

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

+0

啊......我以为Java泛型编译得更像C++模板。谢谢! – rcreswick 2009-01-17 01:51:47

2

因为关于makeString()使用的决定是在编译时进行的,并且基于T可以是任何事物的事实,所以必须是Object版本。想想看。如果您确实Test<String>它将不得不打电话Object版本。因此Test<T>的所有实例将使用makeString(Object)。现在,如果你不喜欢的东西

public abstract class Test<T extends Integer> { 
    ... 
} 

事情可能会有所不同。

2

Josh Bloch的有效的Java有一个很好的讨论,澄清出现的混乱,因为派生对于重载和重载(在子类)方法中的工作方式不同。选择超载方法---这个问题的主题---是在编译时确定的; 覆盖方法中选择是在运行时完成(并因此得到特定类型的对象的知识。)

的书比我的评论更清楚:见 “第41条:使用重载明智”