2

I am trying to understand why the following code is always calling the "out(Object)" method rather than the more specific "out(int|String)" methods. An explanation and a work around would be greatly appreciated.

public static void main(String[] args) {
   Object[] objs = new Object[]{1, 2, 3, "test1", "test2", "test3", 1000L};
   for (Object o : objs) {
      out(o.getClass().cast(o)); // I have also tried passing 'o' directly 
                                 // rather than casting, but that still results 
                                 // in the out(Object) method being called
   }
}

private static void out(int value) {
   System.out.println("integer: " + value);
}

private static void out(String value) {
   System.out.println("string : " + value);
}

private static void out(Object value) {
   System.out.println("object : " + value);
}
user2805089
  • 87
  • 1
  • 10

4 Answers4

5

Overload resolution always happens at compile-time. The compile-time type of o, and of the result of the cast() method, is Object, so it calls that overload.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
2

Overloading works at compile time. The actual method according to the classes of the arguments is thus determined then.

If you want to dispatch at run time, you need to get the arguments to dispatch on into the right position.

Consider:

  foo.bar( baz, quux );

// ^   ^    ^    ^
// |   |    |    |
// |   |     \  /
// |   |      \/
// |   |       overload on the classes of these arguments
// |   |
// |    method name
// |
//  dispatch on this “argument”

Conceptually, the method name and the (overload) arguments form a “message” structure that is fixed at compile time. The message is then “sent” to the object found at run time.

In order to get a dispatching effect in Java, a visitor is often employed.

class PrintMyObjectsVisitor implements MyObjectsVisitor {

    public void visit (MyInteger i) {
        System.out.println( "Integer: " + i.get() );
    }

    public void visit (MyString s) {
        System.out.println( "String: " + s.get() );
    }

    public void visit (MyObject o) {
        System.out.println( "Object: " + o.get() );
    }
}

where

interface MyObjectsVisitor {
    void visit (MyInteger i);
    void visit (MyString s);
    void visit (MyObject o);
}

In order to be visitable, you need to give your objects a fitting accept method. Since you can't extend String or Integer, you need wrappers.

abstract class MyObject {

    abstract Object get ();

    accept (MyObjectsVisitor v) {
        v.visit( this );
    }
}

class MyInteger extends MyObject {
    private int value;

    MyInteger (int value) {
        this.value = value;
    }

    Object get () {
        return Integer.valueOf(this.value);
    }

    accept (MyObjectsVisitor v) {
        v.visit( this );
    }
}

class MyString extends MyObject {
    private String value;

    MyString (String value) {
        this.value = value;
    }

    Object get () {
        return this.value;
    }

    accept (MyObjectsVisitor v) {
        v.visit( this );
    }
}

class MyLong extends MyObject {
    private long value;

    MyLong (long value) {
        this.value = value;
    }

    Object get () {
        return Long.valueOf(this.value);
    }
}

Usage then looks like this:

class MyMain {
    public static void main (String[] args) {
        MyObject[] objects = new MyObject[] {new MyInteger(1),
                                             new MyInteger(2),
                                             new MyInteger(3),
                                             new MyString("test1"),
                                             new MyString("test2"),
                                             new MyString("test3"),
                                             new MyLong(4L)};
        for (MyObject o: objects) {
            o.accept( new PrintMyObjectsVisitor() );
        }
    }
}

As you see, o is now in dispatch position. The accept method of all MyObject classes is compiled to call the right overloaded visit method of the given visitor. Note that for this to work, I needed to repeat the accept method in each subclass of MyObject that actually is implemented in the visitor. (If you are wondering, I didn't use generics here in order to keep the complexity down.)

Note also that you can implement additional MyObjectsVisitors without need for further expansion of the already defined classes.

This can look fundamentally different in other languages.

For example, Common Lisp doesn't use overloading. Methods are not attached to classes but to generic functions. You can dispatch on any and all of the required arguments of a generic function.

(defun dispatch-demo ()
  "A demo function to show dispatch."
  (let ((objects (list 1
                       2
                       3
                       "test1"
                       "test2"
                       "test3"
                       3.14159265)))
    (dolist (object objects)
      (print-with-class object))))

(defgeneric print-with-class (object)
  (:documentation "Prints the given object with its recognized class.
    Recognized classes are defined by the methods of this generic
    function."))

(defmethod print-with-class ((object string))
  (format t "String: ~a~%" object))

(defmethod print-with-class ((object integer))
  (format t "Integer: ~a~%" object))

(defmethod print-with-class ((object t))
  (format t "Object: ~a~%" object))

In languages like Erlang or OCaml, you'd achieve a similar effect with pattern matching.

Svante
  • 50,694
  • 11
  • 78
  • 122
1

Using reflection allows the method to be determined at runtime. Although I needed to change the methods to public rather than private and the int method needed to be changed to Integer. If using this also make sure to change the name of the class "Test" to the name of the your class.

public static void main(String[] args) throws SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
   Object[] objs = new Object[]{1, 2, 3, "test1", "test2", "test3", 1000L};
   for (Object o : objs) {
      try {
         Method m = Test.class.getMethod("out", o.getClass()); // Change Test to NameOfYourClass
         m.invoke(null, o);
      } catch (NoSuchMethodException e) {
         out(o);
      }
   }
}

public static void out(Integer value) {
   System.out.println("integer: " + value);
}

public static void out(String value) {
   System.out.println("string : " + value);
}

public static void out(Object value) {
   System.out.println("object : " + value);
}
user2805089
  • 87
  • 1
  • 10
  • It worked for me, how is it not working as intended? – user2805089 Oct 05 '14 at 22:04
  • Ah, right. My apologies, I had a different class name than yours. I didn't pay much attention to the name of your class, which is Test. It was throwing the NSMF exception, so perhaps you might want to add "Change `Method m = Test.class.getMethod("out", o.getClass());` to `Method m = Your_Class_Name.class.getMethod("out", o.getClass());` or something incase the OP or others make the same stupid mistake. I will delete my first comment. – MarGar Oct 06 '14 at 00:10
1

You are defining an Object array. Everything in it is an Object, including 1, 2, 3, which I assume you want to be primitive types. 1, 2, and 3 are stored as Integers and would need to be cast to an int while casting strings to Strings etc. in the method call such as out((int)o); for each type. Also there is no method to print a Long, which I assume is what you want 1000L to be versus a long.

This answer should provide some insight. Sadly there is no way to cast dynamically. One work around, the easiest, would be to store each type in it's own array.

Another work around would be:

if(o instanceof String)
   out((String) o);
else if(o instanceof Integer)
   out((int) o);
else if(o instanceof Long)
   out(o);

Which can become cumbersome.

Community
  • 1
  • 1
MarGar
  • 465
  • 5
  • 12