Overloaded method selection based on the parameter's real type
Asked Answered
S

7

132

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)?

Sweven answered 15/10, 2009 at 13:19 Comment(0)
P
115

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).

Philodendron answered 15/10, 2009 at 13:28 Comment(2)
Can you explain the spec you quoted please. The two sentences seem to contradict each other. The example above uses instance methods, yet the method being invoked is clearly not being determined at run time.Lachus
@Alex Worden: the compile time type of the method parameters is used to determine the signature of the method to be called, in this case foo(Object). At runtime, the class of the object the method is called on determines which implementation of that method is called, taking into account that it may be an instance of a subclass of the declared type that overrides the method.Philodendron
S
95

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.

Sissel answered 15/10, 2009 at 13:47 Comment(3)
I hope its not too late to say - "one of the best explainations on SOF". Thanks :)Common
I believe if we also added the constructor 'private Confusing(int[] iArray)' it would fail to compile, would it not? Because now there are two constructors with the same specificity.Heterotypic
If i use dynamic return types as function input, it always uses the less specific... said that method that can be used for all possible return values...Hypogynous
I
17

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.

Indubitability answered 15/10, 2009 at 13:23 Comment(0)
M
13

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.

Monomorphic answered 15/10, 2009 at 13:27 Comment(0)
L
4

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! :)

Lachus answered 3/5, 2012 at 21:44 Comment(0)
R
2

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);
Reception answered 15/10, 2009 at 13:33 Comment(0)
Z
1

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.

Zebec answered 3/3, 2015 at 18:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.