This example is referencing the book “Understanding the Java Virtual Machine”.

Firstly, contemplate the expected output of the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StaticDisptach{

static abstract class Human{}

static class Man extends Human{}

static class Woman extends Human{}

public void sayHello(Human people){
System.out.println("hello,people");
}

public void sayHello(Man man){
System.out.println("hello,man");
}

public void sayHello(Woman woman){
System.out.println("hello,woman");
}

public static void main(String[] args){
Human max = new Man();
Human woman = new woman();
StaticDisptach sr = new StaticDisptach();
sr.sayHello(man);
sr.sayHello(woman);
}

}

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
Human man = new Man(); 

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
2
3
4
Human human = (new Random()).nextBoolean()?new Man():new Woman;

sr.sayHello((Man) human);
sr.sayHello((Woman) human);

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class A {
public static void sayHi(Object arg){
System.out.println("object");
}

public static void sayHi(int arg){
System.out.println("int");
}

public static void sayHi(long arg){
System.out.println("long");
}

public static void sayHi(Character arg){
System.out.println("Character");
}

public static void sayHi(char arg){
System.out.println("char");
}

public static void sayHi(char... arg){
System.out.println("char...");
}

public static void sayHi(Serializable arg){
System.out.println("Serializable");
}

public static void main(String[] args){
sayHi('a');
}

}

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!