Overload and Override
Choosing the right method implementation is done at runtime, as you well pointed out, now the signature of the method that is being called is determined at compile time.
Choosing an overload method at compile time
The Java Language Specification (JLS) in section 15.12 Method Invocation Expressions explain in detail the process that the compiler follows to select the correct method to invoke.
There you will notice that this is a compile-time task. JLS says in subsection 15.12.2:
This step uses the method name and types of argument expressions to find methods that are available and applicable. There may be more than one such method, in which case the most specific one is selected.
As a rule, varargs methods are the last if they compete with other candidate methods because they are considered less specific than those that receive the same type of parameter.
To check the nature of compilation time, you can do the following testing.
Declare a class like this and compile it.
public class ChooseMethod { public void doSomething(Number n){ System.out.println("Number"); } }
Declare a second class that calls the method of the first and compile it.
public class MethodChooser { public static void main(String[] args) { ChooseMethod m = new ChooseMethod(); m.doSomething(10); } }
If you call main, the output says Number .
Now add a second more specific overloaded method to the ChooseMethod class and recompile it (but don't recompile another class).
public void doSomething(Integer i) { System.out.println("Integer"); }
If you run main again, the output will be Number .
Basically, because it was decided at compile time. If you recompile the MethodChooser class (the one with the main one) and run the program again, the output will be Integer .
Thus, if you want to force one of the overloaded methods to be selected, the type of arguments must match the type of parameters at compile time, and not just at run time.
Choosing a Runtime Override Method
Again, the signature of the method is determined at compile time, but the actual implementation is determined 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 the second extension 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 will get the output of Number A , and that will be fine, because this method has not been overridden in ChooseMethodB , and therefore the implementation of ChooseMethodA .
Now add an override method to 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 of Number B
Thus, the implementation was chosen at runtime, and the recompilation of the MethodChooser class was not required.