javaparametersoverloadingdispatchmethod-dispatch

is the Java compile-time dispatch of parameters broken?


I already know that Java dispatches methods based on compile-time types. However I have a case, where I expect that to work, but it doesn't.

Consider this simple example:

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

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

and the calling Code:

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

WHY the heck Java is thinking it would be most appropriate to call bar(Object... objects)? Since I have a Map at compileTime everything should work! Why I must explicitly downcast it like foo.bar((Map<String, Object>)map); ??


Solution

  • I tried this program below and got an eror 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 );
       }
    }
    

    So I guess it's the generics that are messing you up. Change the type of the parameter from Object to ?, that worked for me.

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

    Edit: Just to elaborate on this a bit, I put the code back to the original, and added a method like in your example bar(Object...). Here's the Java byte codes that are produced:

    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
    

    You can see in this case the decision has already been made that the Map parameter is not appropriate and the invokevirtual byte code on line 25 wants the Object... version of the call. That's done at compile time, not runtime.

    If I change the code back to my suggestion (using Map<String,?>), then the invokevirual byte code asks for the Map parameter. Your version works too because the cast forces the compiler to emit the invokevirtual for the Map, not the Object... it would normally resolve to.