Overloaded method selection based on the parameter's real type

JavaOop

Java Problem Overview


I'm experimenting with this code:

interface Callee {
    public void foo(Object o);
    public void foo(String s);
    public void foo(Integer i);
}

class CalleeImpl implements Callee
    public void foo(Object o) {
        logger.debug("foo(Object o)");
    }

    public void foo(String s) {
        logger.debug("foo(\"" + s + "\")");
    }

    public void foo(Integer i) {
        logger.debug("foo(" + i + ")");
    }
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

This prints foo(Object o) three times. I expect the method selection to take in consideration the real (not the declared) parameter type. Am I missing something? Is there a way to modify this code so that it'll print foo(12), foo("foobar") and foo(Object o)?

Java Solutions


Solution 1 - Java

> I expect the method selection to take > in consideration the real (not the > declared) parameter type. Am I missing > something?

Yes. Your expectation is wrong. In Java, dynamic method dispatch happens only for the object the method is called on, not for the parameter types of overloaded methods.

Citing the Java Language Specification:

> When a method is invoked (§15.12), the > number of actual arguments (and any > explicit type arguments) and the > compile-time types of the arguments > are used, at compile time, to > determine the signature of the method > that will be invoked (§15.12.2). If > the method that is to be invoked is an > instance method, the actual method to > be invoked will be determined at run > time, using dynamic method lookup > (§15.12.4).

Solution 2 - Java

As mentioned before overloading resolution is performed at compile time.

Java Puzzlers has a nice example for that:

Puzzle 46: The Case of the Confusing Constructor

This puzzle presents you with two Confusing constructors. The main method invokes a constructor, but which one? The program's output depends on the answer. What does the program print, or is it even legal?

public class Confusing {

    private Confusing(Object o) {
        System.out.println("Object");
    }

    private Confusing(double[] dArray) {
        System.out.println("double array");
    }

    public static void main(String[] args) {
        new Confusing(null);
    }
}

Solution 46: Case of the Confusing Constructor

... Java's overload resolution process operates in two phases. The first phase selects all the methods or constructors that are accessible and applicable. The second phase selects the most specific of the methods or constructors selected in the first phase. One method or constructor is less specific than another if it can accept any parameters passed to the other [JLS 15.12.2.5].

In our program, both constructors are accessible and applicable. The constructor Confusing(Object) accepts any parameter passed to Confusing(double[]), so Confusing(Object) is less specific. (Every double array is an Object, but not every Object is a double array.) The most specific constructor is therefore Confusing(double[]), which explains the program's output.

This behavior makes sense if you pass a value of type double[]; it is counterintuitive if you pass null. The key to understanding this puzzle is that the test for which method or constructor is most specific does not use the actual parameters: the parameters appearing in the invocation. They are used only to determine which overloadings are applicable. Once the compiler determines which overloadings are applicable and accessible, it selects the most specific overloading, using only the formal parameters: the parameters appearing in the declaration.

To invoke the Confusing(Object) constructor with a null parameter, write new Confusing((Object)null). This ensures that only Confusing(Object) is applicable. More generally, to force the compiler to select a specific overloading, cast actual parameters to the declared types of the formal parameters.

Solution 3 - Java

Ability to dispatch a call to a method based on types of arguments is called multiple dispatch. In Java this is done with Visitor pattern.

However, since you're dealing with Integers and Strings, you cannot easily incorporate this pattern (you just cannot modify these classes). Thus, a giant switch on object run-time will be your weapon of choice.

Solution 4 - Java

In Java the method to call (as in which method signature to use) is determined at compile time, so it goes with the compile time type.

The typical pattern for working around this is to check the object type in the method with the Object signature and delegate to the method with a cast.

    public void foo(Object o) {
        if (o instanceof String) foo((String) o);
        if (o instanceof Integer) foo((Integer) o);
        logger.debug("foo(Object o)");
    }

If you have many types and this is unmanageable, then method overloading is probably not the right approach, rather the public method should just take Object and implement some kind of strategy pattern to delegate the appropriate handling per object type.

Solution 5 - Java

I had a similar issue with calling the right constructor of a class called "Parameter" that could take several basic Java types such as String, Integer, Boolean, Long, etc. Given an array of Objects, I want to convert them into an array of my Parameter objects by calling the most-specific constructor for each Object in the input array. I also wanted to define the constructor Parameter(Object o) that would throw an IllegalArgumentException. I of course found this method being invoked for every Object in my array.

The solution I used was to look up the constructor via reflection...

public Parameter[] convertObjectsToParameters(Object[] objArray) {
	Parameter[] paramArray = new Parameter[objArray.length];
	int i = 0;
	for (Object obj : objArray) {
		try {
			Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
			paramArray[i++] = cons.newInstance(obj);
		} catch (Exception e) {
			throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
		}
	}
	return paramArray;
}

No ugly instanceof, switch statements, or visitor pattern required! :)

Solution 6 - Java

Java looks at the reference type when trying to determine which method to call. If you want to force your code you choose the 'right' method, you can declare your fields as instances of the specific type:

Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();

You could also cast your params as the type of the param:

callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);

Solution 7 - Java

If there is an exact match between the number and types of arguments specified in the method call and the method signature of an overloaded method then that is the method that will be invoked. You are using Object references, so java decides at compile time that for Object param, there is a method which accepts directly Object. So it called that method 3 times.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSergey MikhanovView Question on Stackoverflow
Solution 1 - JavaMichael BorgwardtView Answer on Stackoverflow
Solution 2 - Javadenis.zhdanovView Answer on Stackoverflow
Solution 3 - JavaAnton GogolevView Answer on Stackoverflow
Solution 4 - JavaYishaiView Answer on Stackoverflow
Solution 5 - JavaAlex WordenView Answer on Stackoverflow
Solution 6 - JavaakfView Answer on Stackoverflow
Solution 7 - JavaAshish ThukralView Answer on Stackoverflow