Method Static Dispatch
This example is referencing the book “Understanding the Java Virtual Machine”.
Firstly, contemplate the expected output of the following code:
1 |
|
The execution result is:
hello,people
hello,people
Understanding the reason the virtual machine executes the overloaded version with the parameter type “Human” involves an understanding of method overloading.
Let’s start by defining two key concepts for the following code:
1 |
|
The Human
in the above code is called the “static type“ of the variable, or “appearance type“, and the following Man
is called the “actual type“ or “runtime type“.
Static and actual types can both change within a program, but the distinction lies in how they change. Changes in static type only occur when used, and the variable’s static type itself remains unchanged. Additionally, the static type is known at compile time. Changes in actual type only become apparent at runtime, and the compiler isn’t aware of an object’s actual type during the compilation phase.
So, what does this sentence mean?
1 |
|
The actual type of the object human
is variable, you cannot know whether it is Man
or Woman
at compile time, you need to wait until run time to know. However, the static type Human
of human
can be temporarily changed through mandatory type conversion during use, but this change can be known at the compile time—call the sayhello
method twice, and the compile time can know whether it is Man
or Woman
.
Therefore, for the previous overload, for the sayHello
method, under the premise that the method receiver has been determined to be the object “sr”, which overloaded version to use depends entirely on the number and data type of the incoming parameters. Compilation is judged by the static type of the parameter instead of the actual type when overloading. Since the static type is known at compile time, Javac compile time determines which overloaded version will be used according to the static type of the parameter during compilation, so sayHello(Human)
is selected as the call target, and the symbol of this method is The references are written to the parameters of the two invokevirtual
instructions in the main()
method.
Furthermore, during the Java compiler’s compilation phase, although it can determine the overloaded version of a method, this version isn’t always unique. More often, the compiler can only determine the most appropriate version. This leads to the concept of overload method resolution priority: (such cases arise primarily due to inherent ambiguity in the semantics of literals.)
1 |
|
After the above code runs, it will generate “char
“
This is easy to understand, because 'a'
is a char type data. But if the sayHi(char arg)
method is commented out, the output becomes:
int
This is because an automatic type conversion occurs, 'a'
not only represents a character, but also the number 97 (Unicode). Now comment out sayHi(int arg)
, the result becomes:
long
This is because two automatic type conversions have occurred, 'a'
is converted from a character to an integer 97, and then converted to a long integer 97L. The automatic type conversion may still continue, that is
char -> int -> long -> float -> double
Please note that there is no casting involving short
and byte
, as their casting is considered unsafe.
If you proceed by commenting out the sayHi(long arg)
method, the result will become:
Character
This is due to autoboxing taking place. If you continue by commenting out the sayHi(Character arg)
method, the output will change to:
Serializable
The reason why the result appears as “Serializable” is because java.lang.Serializable
is an interface implemented by the java.lang.Character
class. When autoboxing cannot find a boxing class but can identify an interface implemented by the boxing class, it performs another round of autoboxing.
A char
can be cast to an int
, but a Character
will not be cast to an Integer
. It can safely be cast only to an interface or superclass it implements or extends.
Here’s a subtle detail: Character
also implements another interface, Comparable<Character>
. If there are two methods, one with a parameter of type Serializable
and the other with a parameter of type Comparable<Character>
, their priorities will be equal. The compiler will show a type ambiguity error and refuse to compile. In such a case, explicit invocation is required.
If you continue by commenting out the sayHi(Serializable arg)
method, the result will become:
Object
Clearly, this is the result of a char
being boxed and then cast to its superclass. If there are multiple superclasses, the search is performed from bottom to top, with Object
having the lowest priority.
Finally, if you comment out the sayHi(Object arg)
method, there will be one last output result:
char…
As can be observed, variable-length arguments have the lowest priority. In this case, the character 'a'
is treated as an element of a char[]
array.
The above example is somewhat extreme and is rarely encountered in practical work outside of using it to challenge job applicants in interviews. Nevertheless, gaining an understanding of such concepts can be helpful for a deeper comprehension of Java.
If you have any more questions or need further clarification, feel free to ask!