A method can be implemented in several classes along the inheritance chain. The JVM decides which method is invoked at runtime. A method can be defined in a superclass and overridden in its subclass. For example, the toString() method is defined in the Object class and overridden in GeometricObject.
Consider the following code:
Object o = new GeometricObject();
System.out.println(o.toString());
Which toString() method is invoked by o? To answer this question, we first introduce two terms: declared type and actual type. A variable must be declared a type. The type that declares a variable is called the variable’s declared type. Here o’s declared type is Object. A variable of a reference type can hold a null value or a reference to an instance of the declared type. The instance may be created using the constructor of the declared type or its subtype. The actual type of the variable is the actual class for the object referenced by the variable. Here o’s actual type is GeometricObject, because o references an object created using new GeometricObject(). Which toString() method is invoked by o is determined by o’s actual type. This is known as dynamic binding.
Dynamic binding works as follows: Suppose an object o is an instance of classes C1, C2, . . . , Cn-1, and Cn, where C1 is a subclass of C2, C2 is a subclass of C3, . . . , and Cn-1 is a subclass of Cn, as shown in Figure below. That is, Cn is the most general class, and C1 is the most specific class. In Java, Cn is the Object class. If o invokes a method p, the JVM searches for the implementation of the method p in C1, C2, . . . , Cn-1, and Cn, in this order, until it is found. Once an implementation is found, the search stops and the first-found implementation is invoked.
The program below gives an example to demonstrate dynamic binding.
Student
Student
Person
java.lang.Object@130c19b
Method m (line 12) takes a parameter of the Object type. You can invoke m with any object (e.g., new GraduateStudent(), new Student(), new Person(), and new Object()) in lines 6–9).
When the method m(Object x) is executed, the argument x’s toString method is invoked. x may be an instance of GraduateStudent, Student, Person, or Object. The classes GraduateStudent, Student, Person, and Object have their own implementations of the toString method. Which implementation is used will be determined by x’s actual type at runtime. Invoking m(new GraduateStudent()) (line 6) causes the toString method defined in the Student class to be invoked.
Invoking m(new Student()) (line 7) causes the toString method defined in the Student class to be invoked; invoking m(new Person()) (line 8) causes the toString method defined in the Person class to be invoked; and invoking m(new Object()) (line 9) causes the toString method defined in the Object class to be invoked.
Matching a method signature and binding a method implementation are two separate issues. The declared type of the reference variable decides which method to match at compile time. The compiler finds a matching method according to the parameter type, number of parameters, and order of the parameters at compile time. A method may be implemented in several classes along the inheritance chain. The JVM dynamically binds the implementation of the method at runtime, decided by the actual type of the variable.