Reputation: 331
From what I understand about dynamic binding, the JVM, at runtime, looks at the actual type of an object and searches for an implementation in that class and works its way up through the inheritance levels.
For example if we have: Vehicle v = new Car();
Assuming that the class Car
extends Vehicle
, we can see that the reference variable type is Vehicle and the object type is Car.
If we were to say: v.start()
:
The JVM would look for the start method implementation first in the Car class and then in the Vehicle class.
An example of this is in this code:
public class scratch{
public static void main(String [] args){
Vehicle v = new Car();
v.start();
}
}
class Vehicle{
public void start(){
System.out.println("Vehicle class");
}
}
class Car extends Vehicle{
public void start(){
System.out.println("Car class");
}
}
The output of this code, as expected is: "Car class"
This is my question: If I take out the start method from class Vehicle, completely erase it, the program won't run anymore. From what I understand about dynamic binding, the JVM should still look at the actual type of the object (which in this case is Car) and still run the car implementation of the start method. However, it does not do this.
Why?
Upvotes: 0
Views: 197
Reputation: 178343
The problem with removing start()
from Vehicle
has to do with polymorphism. In Vehicle
, if you define start()
here, then that is what says that all Vehicle
s, even subclasses, have that method.
If you remove start()
from Vehicle()
, then it can't be guaranteed that any Vehicle
has a start()
method, even though we know it's a Car
that does have start()
. What if there is a HorselessCarriage
class that extends Vehicle
but doesn't define start()
? Then, there is no start()
method. For that reason, if there's no start()
method on Vehicle
, you can't call start()
on a Vehicle
variable.
The whole point of being able to call start()
on a Vehicle
is to ensure that any Vehicle
implementation has a start()
method to call.
UPDATE
The JVM takes the runtime type of the object and looks for a method matching the signature of the method call. If not found, it walks up the inheritance tree to the superclass and looks for the method there.
More details are given in the JLS, Section 15.12.4.4:
Let X be the compile-time type of the target reference of the method invocation. Then:
If class S contains a declaration for a non-abstract method named m with the same descriptor (same number of parameters, the same parameter types, and the same return type) required by the method invocation as determined at compile time (§15.12.3), then:
If the invocation mode is super or interface, then this is the method to be invoked, and the procedure terminates.
If the invocation mode is virtual, and the declaration in S overrides (§8.4.8.1) X.m, then the method declared in S is the method to be invoked, and the procedure terminates.
If the invocation mode is virtual, and the declaration in S does not override X.m, and moreover X.m is declared abstract, then an AbstractMethodError is thrown.
Otherwise, if S has a superclass, this same lookup procedure is performed recursively using the direct superclass of S in place of S; the method to be invoked is the result of the recursive invocation of this lookup procedure.
Here, S
appears to be the runtime type of the object.
Upvotes: 1
Reputation: 11250
In nutshell, JVM
need one endpoint to begin the search for the reference of the start
method, not matter the object type have a method that you want to invoke, JVM
need a mirror to ensure you are trying to call an existing method.
Upvotes: 1
Reputation: 67514
I think a simple way to see the problem is to introduce a method. Here's how the method is defined:
public void callStart(Vehicle vehicle) {
vehicle.start();
}
This method lets you pass in a concrete Car
or a concrete Vehicle
.
Lets pretend that Java lets you compile this code. If Java let you do this for a Vehicle
without a start()
method, then you'd have to discover the error at runtime. But Java saves you some time by letting you know when you've compile that you have an error.
This is different from some dynamic languages like Javascript. If this were JavaScript you could pass in a concrete Vehicle
and then you'd have to discover your error at runtime. Another difference is that in JavaScript, you could pass in a concrete Car
and it would work without error. This is called duck typing and is a feature Java doesn't have.
Upvotes: 0
Reputation: 24
But once you remove the method, Vehicle no longer has any "start" functionality: it is an unknown method in the Vehicle class, and you are accessing it through a Vehicle reference. To make java do what you want, you could do
abstract class Vehicle
{
public abstract void start();
}
Your code should then work again, because all children of Vehicle are now guaranteed a start method. But as it stands in your example, once the start method is removed, there's no guarantee that some previous statement hasn't created some other Vehicle descendant, e.g., Motorcycle without a start method and assigned it to your v reference.
Upvotes: 0
Reputation: 2653
When you have the start() method in the Vehicle class, Car is Overriding that method. When you remove the start() method from Vehicle, you are no longer overriding that method. So calling v.start() has no method to call. This is why you should use @Override, so that it is clear in the code what is happening. In order to call start() on Car in the case where there is no start() method in Vehicle, you'd first have to cast the vehicle to the Car class.
Upvotes: 0