Fun With Overloading

February 21st, 2009

One of my students ran into an interesting issue with autoboxing and the List APIs. Before I blog on that example, I wanted to start with something more generic to help flush out overloading further than I had time for in class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    public static void main(String[] args) {
        String s = new String();
        Object o = new String();
 
        wombat(s);
        wombat(o);
    }
 
    static void wombat(Object o) {
        System.out.println("Object");
    }
 
    static void wombat(String s) {
        System.out.println("String");
    }

What will print when the above is executed? More importantly, why?

The first call, “wombat(s)”, should be pretty simple. There is a method definition that takes a String, so it’s not a huge surprise that “String” is printed out (one could argue that this call would also fulfil the requirements to call the Object version of wombat, which will be address later in this entry).

But what about the call to “wombat(o)”? There are two options:

  • The object type inside of o will be checked, which in this case is String, and execute the corresponding version and printing “String”
  • The variable type of o will be checked and execute that implementation, printing “Object”

The result is the second option; the chosen method is determined by the type of the variable, not its contents. But what happens when we take the variable out of the picture?

1
2
wombat(new Object());
wombat(new String());

The first example is trivial; there is only one candidate that could accept an Object. The latter, however, could technically qualify for either implementation of wombat. In that case, the most specific implementation of wombat will be run. In this case, String is more specific than Object (in other words, it is a subclass of Object), so the result will print “String”.

Now consider a slightly more complicated example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public static void main(String[] args) {
        wombat(null);
    }
 
    static void wombat(Throwable o) {
        System.out.println("Throwable");
    }
 
    static void wombat(Error e) {
        System.out.println("Error");
    }
    static void wombat(Exception e) {
        System.out.println("Exception");
    }

The change here is that there are two overloaded methods that accept subclasses of the third (remember Exception and Error are both direct subclasses of Throwable).

What prints in this case, where simply “null” is passed to wombat? The “more specific” argument can’t be applied, since both Error and Exception are more specific than Throwable, however they are unrelated to each other.

What actually happens is a compile time error, indicating there is an “Ambiguous method call”. In other words, there is no way to deterministically decide which implementation of wombat to call (the Error version or the Exception version).

One last detail, such a situation doesn’t prevent you entirely of passing null to these methods. You simply need to indicate which implementation you want to run:

1
wombat( (Exception) null);

It may seem weird to cast null to something, but it serves a way to disambiguate the call, indicating a particular overloaded implementation to execute.

Comments are closed.