Java overloading rules

Overloading vs Overriding

The selection of the right implementation of method is done at runtime as you well pointed out, now the signature of the method to be invoked is decided at compile time.

Overloading Method Selection at Compile Time

The Java Language Specification (JLS) in section 15.12 Method Invocation Expressions explains in detail the process that the compiler follows to choose the right method to invoke.

There, you will notice that this is a compile-time task. The JLS says in subsection 15.12.2:

This step uses the name of the method and the types of the argument expressions to locate methods that are both accessible and applicable There may be more than one such method, in which case the most specific one is chosen.

Typically, varargs methods are the last chosen, if they compete with other candidate methods, because they are considered less specific than the ones receiving the same parameter type.

To verify the compile-time nature of this, you can do the following test.

Declare a class like this and compile it (i.e. javac ChooseMethod.java).

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

Declare a second class that invokes a method of the first one and compile it (i.e. javac MethodChooser.java).

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

If you run the program (i.e. java MethodChooser), the output says Number.

Now, add a second more specific overloaded method to the ChooseMethod class, and recompile it (but do not recompile the other class).

public void doSomething(Integer i) {
 System.out.println("Integer");
}

If you run the main again, the output is still Number.

Basically, because it was decided at compile time. If you recompile the MethodChooser class (the one with the main), and run the program again, the output will be Integer.

As such, if you want to force the selection of one of the overloaded methods, the type of the arguments must correspond with the type of the parameters at compile time, and not only at run time.

Overriding Method Selection at Run time

Again, the signature of the method is decided at compile time, but the actual implementation is decided at runtime.

Declare a class like this and compile it.

public class ChooseMethodA {
   public void doSomething(Number n){
    System.out.println("Number A");
   }
}

Then declare a second extending class and compile:

public class ChooseMethodB extends ChooseMethodA {  }

And in the MethodChooser class you do:

public class MethodChooser {
    public static void main(String[] args) {
        ChooseMethodA m = new ChooseMethodB();
        m.doSomething(10);
    }
}

And if you run it you get the output Number A, and this is Ok, because the method has not been overriden in ChooseMethodB and therefore the implementation being invoked is that of ChooseMethodA.

Now, add an overriden method in MethodChooserB:

public void doSomething(Number n){
    System.out.println("Number B");
}

And recompile just this one, and run the main method again.

Now, you get the output Number B

As such, the implementation was chosen at runtime, and not recompilation of the MethodChooser class was required.


First question:

Your assumption is correct. The second call to f() will call the varargs method. You can get the compiler to call the second method with:

private void f(int a) {
    f(a, null);
}

Second question:

Yes. However, you can't extend an interface. If you change A to an abstract class, things will compile.