【问题标题】:is the Java compile-time dispatch of parameters broken?参数的 Java 编译时分派是否损坏?
【发布时间】:2014-09-15 01:38:42
【问题描述】:

我已经知道 Java 基于编译时类型调度方法。但是我有一个案例,我希望它可以工作,但它没有。

考虑这个简单的例子:

class Foo{ 
    void bar(Object... objects) { //do something }

    void bar(Map<String, Object> map) { //do something else }
}

和调用代码:

Foo foo = new Foo();
HashMap<String, T> map = createSomeHashMap(); //wil generate HashMap
foo.bar(map);

为什么 Java 认为调用 bar(Object... objects) 最合适? 因为我在编译时有一个地图,所以一切都应该工作!为什么我必须像foo.bar((Map&lt;String, Object&gt;)map); 一样明确地贬低它??

【问题讨论】:

  • Java 不会动态地(在运行时)分派参数。方法签名的选择完全在编译期间发生。不,选择没有“损坏”:它由 JLS 明确定义。
  • 他确实说过“编译时类型”。 ;) 我认为他至少有一点是对的。
  • @markspace 完全正确!我什至不要求“运行时调度”!我觉得 user28... 甚至不阅读我的问题,而只是阅读主题(我承认我有一个错误)

标签: java parameters overloading dispatch method-dispatch


【解决方案1】:

我在下面尝试了这个程序并得到了一个错误Type mismatch: T cannot be converted to Object

public class DispatchTest
{

   private void bar( HashMap<String, Object> map )
   {
   }

   public static void main( String[] args )
   {
      test();
   }

   private static <T> void test()
   {
      DispatchTest dt = new DispatchTest();
      HashMap<String,T> map = new HashMap<>();
      dt.bar( map );
   }
}

所以我猜是泛型把你搞砸了。将参数的类型从 Object 更改为 ?,这对我有用。

private void bar( HashMap<String, ?> map )
{
}

编辑:为了详细说明这一点,我将代码恢复为原始代码,并添加了一个方法,如您的示例 bar(Object...)。以下是生成的 Java 字节码:

private static <T extends java/lang/Object> void test();
Code:
   0: new           #3                  // class quicktest/DispatchTest
   3: dup
   4: invokespecial #4                  // Method "<init>":()V
   7: astore_0
   8: new           #5                  // class java/util/HashMap
  11: dup
  12: invokespecial #6                  // Method java/util/HashMap."<init>":()V
  15: astore_1
  16: aload_0
  17: iconst_1
  18: anewarray     #7                  // class java/lang/Object
  21: dup
  22: iconst_0
  23: aload_1
  24: aastore
  25: invokevirtual #8                  // Method bar:([Ljava/lang/Object;)V

  28: return

您可以看到,在这种情况下,已经决定 Map 参数不合适,并且第 25 行的 invokevirtual 字节码需要 Object... 版本的调用。这是在编译时完成的,而不是运行时。

如果我将代码改回我的建议(使用Map&lt;String,?&gt;),则invokevirual 字节码会要求提供Map 参数。您的版本也可以工作,因为强制转换强制编译器为 Map 发出 invokevirtual,而不是 Object...它通常会解析为。

【讨论】:

  • 我无法更改 Foo 类的方法,因为它属于我使用的 API (ElasticSearch)。我什至不想有一个可行的解决方案(通过铸造解决),但只是感兴趣,为什么这不起作用。我觉得它不可能是通用的,因为它们被删除了。您甚至不允许在同一个类中编写两个方法,如下所示:void bar(List&lt;String&gt; strings)void bar(List&lt;Integer&gt; integers),因为对于 java,它在运行时是相同的:void bar(List&lt;Object&gt; list)
  • 你甚至不允许编译你的程序(至少在 Eclipse 中)。但我的确实编译了!
  • @Rafael T:是的,泛型在运行时被删除,但是在编译时决定调用哪个方法。因此,泛型融入其中,第一个方法 bar(Object...) 由编译器调用。
  • 非常感谢里面的内容,但我现在真的很困惑。但是,如果 ANY T (至少)是一个对象,这有意义吗? Java 中的这些泛型实在是太糟糕了……它们永远不会按预期工作!
  • 任何T 都不是对象。泛型是invariant, not covariant。例如,类型 与类型 不匹配,因为包含 Objects 的类型可能具有 Numbers 或 JButtons,而 Strings 是涉及泛型的不同类型。
猜你喜欢
  • 2016-04-18
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多