Overloading Methods with Varargs

Don’t do it. It is a bad idea.

While item 41 “Use overloading judiciously” and item 42 “Use varargs judiciously” of Effective Java should have warned you about this issue, I stumbled upon how confusing the combination of two could be. Consider this program:

public class Overload {
    public void method(Parent obj) {
        System.out.println("method(Parent obj)");
    }

    public void method(Child obj) {
        System.out.println("method(Child obj)");
    }

    public static void main(String[] args) {
        Overload overload = new Overload();
        overload.method(new Parent());
        overload.method(new Child());
    }
}

Given that Parent is the super class of Child, this program will print

method(Parent obj)
method(Child obj)

The overload resolution prefers specialization over generalization. Intuitive, right?

What if we add varargs to the second overloaded method?

public class Overload {
    public void method(Parent obj) {
        System.out.println("method(Parent obj)");
    }

    public void method(Child obj, String... extras) {
        System.out.println("method(Child obj)");
    }

    public static void main(String[] args) {
        Overload overload = new Overload();
        overload.method(new Parent());
        overload.method(new Child());
    }
}

This time it will print

method(Parent obj)
method(Parent obj)

Counter-intuitive? JLS 15.12.2 states clearly that compiler performs overload resolution while allowing varargs in the last phase. So the method invocation

    overload.method(new Child());

will be resolved to

    public void method(Parent obj)

before

    public void method(Child obj, String... extras)

even gets considered by compiler. If you want to call the second overloaded method, you have to make it explicit at the caller site

    overload.method(new Child(), (String[])null);

Now you have to hunt down every client of your class and make this change. So it turns out that the seemingly harmless method signature change is effectively a backward-incompatible API change.

This problem happened in a class maintained by multiple people, each making patches to the class. Although their changes looked fine separately, when their changes were finally all merged, the problem emerged. The nasty aspect of this problem is that it is context-dependent. Now a patch is not guilty only if we also examine all other patches. In a team, this kind of problem will prevent it from scaling.

The bottom line is, no matter how innocent overloaded or varargs methods might look, add them to your classes judiciously.

Creative Commons License
This blog by Che-Liang Chiou is licensed under a Creative Commons Attribution 4.0 International License.