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 MyObjectsVisitor
s 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.