Dynamic Method Dispatch In Java (Runtime Polymorphism) + Examples
Dynamic method dispatch is a fundamental concept in object-oriented programming, particularly in Java, where the decision of which method to call is made at runtime rather than compile time. This mechanism allows Java to execute the overridden method of a subclass based on the actual object type, enabling runtime polymorphism.
In simpler terms, when a superclass reference variable points to a subclass object, the overridden method invoked is determined dynamically at runtime. This flexibility is a cornerstone of Java's polymorphic behavior, making programs more extensible and adaptable.
In this article, we will discuss dynamic method dispatch in Java, its mechanics, examples, and benefits, and provide a comparison with static binding for a clearer understanding.
What Is Dynamic Method Dispatch In Java Programming?
The concept of dynamic method dispatch in Java programming arises from the inheritance model, where a superclass can contain methods with a general definition and its subclasses can provide their specific implementations (this is the concept of method overriding).
- Dynamic method dispatch in Java, also known as runtime polymorphism, is a mechanism that ensures the correct overridden method is executed based on the actual object type at runtime (and not compile time). This is, even if the reference to call the method is of the superclass type (type of object is the base class).
- Normally, having methods with the same name/ identifier in multiple classes can lead to ambiguity problems in determining which method to invoke in response to a method call.
- Java resolves the ambiguity of which method to call during runtime, relying on the Java Virtual Machine (JVM) to identify and execute the appropriate method implementation dynamically (based on the type of object referenced at that moment).
For example, consider a class Parent and its subclass Child, and both have a method called speak(). Depending on whether the reference variable is assigned a Parent or Child object, the JVM dynamically decides which speak() method implementation to execute.
But how does the Java programming language decide which method to invoke at runtime? The answer lies in upcasting.
Without upcasting, dynamic method dispatch wouldn’t be possible, as the reference and object types would always match, and method resolution would happen at compile time instead of runtime. This compile-time resolution is known as static binding, and we’ll explore the differences between static and dynamic binding in more detail in a later section.
For now, let’s understand upcasting and its role in dynamic method dispatch.
What Is Upcasting & Its Importance In Dynamic Method Dispatch In Java?
Upcasting refers to the process of casting a subclass object to a superclass reference type. It enables polymorphism by allowing the superclass reference type to hold a subclass object, but the actual method execution is determined dynamically.
To better understand this concept, imagine we have a class named Parent and its child class (which inherits from Parent) named Child. Now, we have two ways to create an object in reference to the Parent class.
- Direct Instantiation: In this approach, we create an instance of the Parent class and assign it to a reference variable obj of type Parent. It's a straightforward instantiation of the Parent class. In this case, any methods or properties specific to Parent can be accessed through this reference.
- Upcasting: In this approach, we're creating an object of the Child class but assigning it to a reference variable obj of the Parent type.
-
- This process of casting a reference variable of a subclass to its superclass type is known as upcasting.
- It allows a superclass reference variable to hold the address of a subclass object, enabling polymorphic behavior and dynamic method dispatch in Java as this superclass reference variable, although referring to a specific subclass object, can dynamically invoke overridden methods of the subclass at runtime.
The second approach (upcasting) enables a superclass reference variable to hold the address of a subclass object, ensuring the correct method is dynamically determined and executed.
This dynamic decision-making capability allows for the precise invocation of the appropriate method implementation at runtime, popularly known as runtime polymorphism.
Syntax Of Upcasting For Dynamic Method Dispatch In Java:
SuperclassReference = new Subclass(); // Upcasting
Here,
- The term SuperclassReference is the reference variable of the superclass type. It can refer to objects of its own class or any of its subclasses.
- The new keyword is used for the instantiation/ creation of a new object, and Subclass() is the constructor of the class whose object is being created. Together, these create an object of the subclass type.
Let's consider a real-life analogy using a class hierarchy representing vehicles. Suppose we have a superclass Vehicle and a subclass Car that extends Vehicle.
Code Example:
Output:
Driving a vehicle
Driving a car
Explanation:
In the Java code example,
- We first create a class called Vehicle, containing a method drive(), which prints a string message ("Driving a vehicle") to the console using out.println.
- Then, we create another class named Car, which extends Vehicle class (or inherits from Vehicle). Inside:
- We define a drive() method that prints another string message ("Driving a car") to the console. This method overrides the superclass method.
- It also has a drift() method that prints the message– "Performing a drift!", to the console.
- Moving on, we define the Main class with the main() method to begin the execution of the program.
- Inside main(), we first create an instance of the Vehicle class and assign it to vehicle1 reference variable of type Vehicle (direct instantiation).
- Then, we create an object of the Car class and assign that to vehicle2 reference variable of type Vehicle. This is upcasting illustrating how a subclass (Car) object can be treated as its superclass (Vehicle).
- Next, we call the drive() method using vehicle1, which straightforwardly invokes the method from the superclass.
- After that, we call the drive() method using vehicle2. As a result, the overridden version in the Car class is executed because the actual object type is Car. This demonstrates dynamic method dispatch.
- We then have a commented line, where we call the drift() method using vehicle2. This results in a compilation error because the drift() method is not defined in the Vehicle class, and the reference type (Vehicle) restricts access to subclass-specific methods.
- This means that even though vehicle2 refers to a Car object, the reference type is Vehicle, so methods specific to Car, such as drift(), are not accessible through this reference. The compiler will throw an error when you attempt to call drift() on vehicle2.
Now that we understand dynamic method dispatch in Java let’s see how upcasting is essential in making this mechanism work.
Want to expand your knowledge of Java programming? Check out this amazing course and become the Java developer of your dreams.
Why Is Upcasting Necessary for Dynamic Method Dispatch?
Dynamic method dispatch relies on upcasting to create a scenario where a method cannot be resolved at compile time. When a subclass object is referenced using a superclass variable, the exact method to be invoked is determined at runtime, enabling polymorphic behavior.
For example:
Parent obj = new Child();
Here, obj is a reference of the Parent type, but the actual object it points to is of the Child class. This creates the ambiguity necessary for dynamic method dispatch to come into play, allowing the JVM to resolve the method dynamically based on the object type.
Dynamic Method Dispatch In Java Example: Animal
Let’s take another scenario with a superclass Animal and two subclasses, Dog and Cat. In this example Java program, each subclass overrides the common makeSound() method from the Animal class while also adding unique behaviors of its own.
Code Example:
Output:
Woof!
Meow!
Explanation:
In this example Java code, we define an Animal class with a makeSound() method that all animals share. Following that, we define the Dog and Cat classes, which are the subclasses of Animal, and each class overrides the makeSound() method to provide its own implementation.
Then in the main() method:
- We create two references of type Animal — animal1 and animal2 — but assign them to Dog and Cat objects, respectively.
- This is upcasting, where we treat subclasses (Dog and Cat) as their superclass (Animal). Remember, upcasting is essential for dynamic method dispatch to function properly.
- Then, we call the makeSound() method on animal1, and it prints "Woof!", even though the reference type is Animal. This is because animal1 actually points to a Dog object, and the overridden method in the Dog class is executed at runtime.
- Similarly, calling makeSound() on animal2 prints "Meow!", as animal2 refers to a Cat object.
- Next, we have two commented lines where we call the wagTail() method on animal1 (animal1.wagTail()) and the jump() method on animal2 (animal2.jump()).
- If run, these lines will throw an error because the Animal class does not know about methods specific to its subclasses. These methods (wagTail() and jump()) are only available within the Dog and Cat classes, respectively.
- Since animal1 and animal2 are reference variables of type Animal, they cannot access methods defined in their specific subclass without explicit casting.
Dynamic Method Dispatch In Java With Data Member
While Java allows methods to be overridden in subclasses, which is a core part of runtime polymorphism, data members (variables) behave differently. Unlike methods, variables cannot be overridden in Java. This means that while a subclass can inherit variables from its superclass, it cannot replace or redefine them.
Each class maintains its own copy of the inherited variables, and the reference type of an object determines which methods are accessible, but variables always refer to the member declared in the reference type, not the actual object.
Let's understand it through an example. Suppose we have a superclass Vehicle and its two subclasses Car and Truck. Each class has a name variable representing the type of vehicle and a getDescription() method that provides a description of the vehicle.
Code Example:
Output:
Vehicle
This is a car.
Vehicle
This is a truck.
Explanation:
In this example, we have a class hierarchy representing vehicles. The Vehicle class has a name variable and a getDescription() method. The subclasses Car and Truck each inherit from Vehicle, with their own versions of the name variable and the getDescription() method.
- The Vehicle class has a variable name initialized to "Vehicle".
- The Car class has its own name variable, initialized to "Car", and similarly for the Truck class.
- Despite the object reference (vehicle1 and vehicle2) pointing to instances of Car and Truck, both vehicle1.name and vehicle2.name output "Vehicle".
- This happens because the name variable is not overridden and is tied to the reference type (Vehicle), not the actual object type (Car or Truck).
- The getDescription() method in Vehicle returns a generic description of the vehicle, while both the Car and Truck classes override this method to provide their specific descriptions.
- Despite the references being of type Vehicle when getDescription() is called, Java uses dynamic dispatch to call the correct method based on the actual object type (i.e., Car or Truck), not the reference type.
- Therefore, vehicle1.getDescription() prints "This is a car." and vehicle2.getDescription() prints "This is a truck.".
Here is your chance to top the leaderboard while practising your coding skills: Participate in the 100-Day Coding Sprint at Unstop.
Advantages Of Dynamic Method Dispatch In Java
Dynamic method dispatch offers several pivotal advantages, which include:
- Run-time Polymorphism: Dynamic method dispatch in Java enables run-time polymorphism, allowing the program to invoke the correct overridden method based on the actual object type. This enhances flexibility by allowing a single interface to serve different classes, each with its unique implementation.
Example: Suppose we have a superclass Shape with a method draw(), and its subclasses Circle and Rectangle override this method with their specific implementations. At runtime, the correct draw() method based on the actual object type (Circle or Rectangle) is invoked.
Shape shape = new Circle();
shape.draw(); // Calls Circle's draw method at runtime
- Code Reusability: Method overriding allows subclasses to provide a specific implementation of a method already defined in the superclass. This promotes the reuse of method names across different classes, reducing redundancy and making code more maintainable.
Example: A superclass Animal can have a method makeSound(), and subclasses like Dog and Cat can override this method to produce their unique sounds, all using the same method name makeSound(). - Flexibility in Method Implementation: Dynamic method dispatch in Java allows subclasses to define their specific behavior while adhering to a common interface. This ensures a consistent method signature while providing flexibility for subclasses to implement their own logic.
Example: An interface Shape might have a method calculateArea(). Different shapes, like Circle and Rectangle can implement this interface and define their unique logic for calculating the area while maintaining the common interface structure. - Enhanced Maintainability: Dynamic method dispatch in Java allows for easier modifications and enhancements in code without affecting the superclass or other subclasses. This separation between superclass and subclasses enables developers to modify specific behaviors in subclasses independently, promoting maintainability.
Example: If there's a need to change the behavior of a specific subclass without altering the superclass or other subclasses, developers can easily modify that subclass's overridden method. - Promotes Extensibility: Subclasses can extend the functionality of the superclass by adding new methods or modifying existing behaviors. This allows new features to be integrated without altering the core functionality of the superclass, fostering system extensibility.
Example: A superclass Vehicle might have a method startEngine(), and subclasses like Car and Bike can add their unique methods accelerate() or applyBrakes() to extend the functionality provided by the superclass.
Disadvantages Of Dynamic Method Dispatch In Java
While dynamic dispatch has multiple advantages, there are some downsides that we must consider.
- Performance Overhead: Dynamic method dispatch involves determining the appropriate method to call at runtime based on the actual object type. This runtime resolution can introduce a performance overhead compared to static method calls, which are resolved at compile time.
- Potential for Runtime Errors: Since method calls are resolved at runtime based on the actual object type, there is a potential for runtime errors if the methods involved in runtime resolution are not properly overridden in a subclass or if there are potential errors in the method implementation.
Static vs. Dynamic Binding & Dynamic Method Dispatch In Java
In Java, binding refers to how methods or variables are connected to their calls. This can occur either at compile time (static binding) or at runtime (dynamic binding), and it largely depends on the method type and the reference used.
Static Binding
In static binding, the method call is resolved at compile time based on the reference variable's type. This is also known as early binding. Static binding is used for:
- Private methods
- Static methods
- Final methods
- Overloaded methods
Since the method resolution happens at compile time, it’s determined by the type of the reference variable, not the actual object type.
Dynamic Binding
In dynamic binding, method calls are resolved at runtime based on the actual object type, not the reference variable. This is also known as late binding. Dynamic binding is used primarily for overridden methods (instance methods). This behavior is a https://unstop.com/blog/features-of-java, allowing method overriding in subclasses.
Impact Of Access Modifiers & Static On Binding
- Private Methods: These methods are bound statically. Since they can’t be overridden, method calls are resolved at compile time based on the reference type.
- Static Methods: Like private methods, static methods are also bound statically. The call is resolved based on the reference type, not the actual object type.
- Final Methods: Since final methods can’t be overridden, they are statically bound to the class at compile time.
Now, let's take a look at the differences between static and dynamic binding:
Basis | Static Binding | Dynamic Binding |
---|---|---|
Definition |
Method call is resolved at compile time. |
Method call is resolved at runtime. |
Occurrence |
Occurs for overloaded methods, private methods, static methods, and final methods. |
Occurs for overridden methods (instance methods). |
Type of Methods |
Can be static, private, final, or overloaded. |
Typically instance or overridden methods. |
Determination |
Determined by the reference type at compile time. |
Determined by the actual object type at runtime. |
Key Use Cases |
Early checking of method existence and type (compile-time). |
Enables polymorphic behavior, method overriding, and runtime method resolution. |
Performance |
Faster since method resolution happens at compile time. |
Slight performance overhead due to runtime method resolution. |
Binding Time |
Early binding (done at compile time). |
Late binding (done at runtime). |
Method Visibility |
Can be used for private and static methods, where visibility is determined at compile time. |
Only applicable for methods that can be overridden or are virtual. |
Flexibility |
Less flexible since methods can't be changed dynamically. |
More flexible, allowing subclasses to provide their own implementations. |
Code Example For Static Binding
In static binding, the reference type determines the method call. Suppose we have a superclass Employee and two subclasses, Manager and Engineer. Each class will have a static method getRole() that returns the role of the employee.
Code Example:
Output:
Employee
Employee
Explanation:
In the sample Java code,
- The getRole() method is static in the Employee, Manager, and Engineer classes.
- Static methods are bound at compile time based on the reference type, so employee1.getRole() and employee2.getRole() both return "Employee", even though the actual objects are of type Manager and Engineer.
Code Example For Dynamic Binding
In dynamic binding, the method call is resolved at runtime based on the actual object type. Consider the same example discussed above, but this time, we don't have static methods in the classes.
Code Example:
Output:
Manager
Engineer
Explanation:
In the sample Java code,
- The getRole() method is an instance method, which means it can be overridden by subclasses.
- In this case, the actual object types (Manager and Engineer) determine which getRole() method is invoked, demonstrating dynamic binding.
- The method resolution occurs at runtime based on the object's actual type, allowing for different behavior for each subclass (and subclass methods).
Need more guidance on how to become a Java developer? You can now select an expert to be your mentor here.
Conclusion
Dynamic method dispatch in Java, also known as runtime polymorphism, allows methods to be invoked based on the actual type of the object at runtime. This process is a key component of Java's polymorphic behavior, enabling overriding of methods in subclasses.
- It relies on upcasting, where a subclass object is treated as an object of its superclass, providing flexibility and enhancing the maintainability of code.
- Through dynamic method resolution, Java can determine the correct version of a method to execute, even when the reference variable is of the superclass type.
- However, dynamic dispatch only applies to methods that are overridden in subclasses. Variables, on the other hand, are bound statically, and they cannot be overridden.
Dynamic dispatch offers numerous advantages, including code reusability, flexibility, and extensibility in method implementation. However, it comes at the cost of some performance drawbacks and the possibility of runtime errors.
Java employs static binding for private, static, and final methods, where method call resolution is based on the type of reference variable at compile time. On the other hand, dynamic binding, used for virtual methods, resolves method calls based on the actual class of the object at runtime, enabling polymorphic behavior and method overriding.
Also read: Top 100+ Java Interview Questions And Answers (2024)
Frequently Asked Questions
Q1. What is the difference between overriding and dynamic method dispatch?
Overriding and dynamic method dispatch are closely related concepts but serve different roles in Java.
- Overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. It allows the subclass to specialize or change the behavior of the inherited method.
- Dynamic method dispatch, on the other hand, is the mechanism that determines which overridden method to invoke at runtime based on the actual type of the object rather than the type of reference variable.
Consider a scenario involving shapes, where we have a superclass Shape and two subclasses, Circle and Square. Each shape has a method draw() that prints a message specific to that shape.
Code Example:
Output:
Drawing a circle
Drawing a square
Explanation:
In this example:
- Both Circle and Square classes override the draw() method defined in the Shape class, providing their specific implementations. This represents the concept of method overriding.
- When shape1.draw() is called, even though shape1 is of type Shape, it refers to a Circle object. At runtime, the actual object type (Circle) is considered, and the overridden draw() method in the Circle class is executed. This represents dynamic method dispatch in Java. The same dynamic dispatch occurs for shape2.draw() with the Square object.
Q2. Is Static Dispatch faster than dynamic dispatch?
Yes, static dispatch is generally faster than dynamic dispatch in Java. Static dispatch involves method calls that can be resolved at compile time based on the reference type. This early resolution allows the compiler to directly link the method call to the appropriate method implementation.
On the other hand, dynamic dispatch involves method calls that are resolved at runtime based on the actual type of the object. This requires additional runtime overhead to determine the correct method implementation to call, which can result in slightly slower performance in this case compared to static dispatch.
Q3. What is the difference between dynamic binding and dynamic method dispatch?
Dynamic binding refers to the process of determining the method implementation to invoke at runtime based on the actual class of the object. Dynamic method dispatch is a specific mechanism of dynamic binding where the method call is resolved to an overridden method at runtime. Essentially, dynamic method dispatch is the runtime resolution of method calls that have been overridden in subclasses.
Basis | Dynamic Binding | Dynamic Method Dispatch |
---|---|---|
Definition |
Process of determining method implementation at runtime. |
Specific case of dynamic binding where overridden methods are resolved at runtime. |
Occurrence |
Encompasses the entire process of resolving method calls at runtime. |
Refers specifically to resolving overridden method calls at runtime. |
Scope |
Includes fields, method calls, and other dynamic resolutions. |
Focuses on resolving overridden methods. |
Key Use Cases |
Achieving polymorphic behavior and method overriding. |
Resolving which overridden method to call at runtime. |
Syntax |
Conceptual, not defined by code. |
ParentClass obj = new ChildClass(); obj.overriddenMethod(); |
Q4. Are private, static, or final methods subject to dynamic method dispatch in Java?
No, private, static, and final methods do not participate in dynamic method dispatch. These methods are bound statically during compile time.
- Private methods are not accessible to subclasses and cannot be overridden, so they aren't part of dynamic dispatch.
- Static methods are tied to the class and not to specific object instances, so they are resolved based on the reference type at compile time.
- Final methods cannot be overridden, so there's no need for dynamic dispatch to resolve the method.
Q5 . Does dynamic method dispatch work with overloaded methods?
No, dynamic method dispatch in Java does not work with overloaded methods. Overloading resolves method calls at compile time based on the method signature (number or type of parameters). In contrast, dynamic dispatch applies only to overridden methods where the method's implementation is determined at runtime based on the actual object type.
Q6. Can dynamic method dispatch occur across different classes in the inheritance hierarchy?
Yes, dynamic method dispatch in Java works across different classes in the inheritance hierarchy. When a method is overridden in a subclass, it retains the same signature as the superclass method. At runtime, when the method is called via a reference to the superclass, the actual class of the object determines which method to execute, demonstrating polymorphism across the hierarchy.
Let's consider an example using a business-oriented scenario, such as employees in an organization to understand dynamic method dispatch across different classes in the inheritance hierarchy.
Code Example:
Output:
Manager information
Engineer information
Explanation:
In this example, we have a class hierarchy representing employees.
- The Employee class has a method displayInfo(), which is overridden in its subclasses Manager and Engineer.
- In the Main class, we create instances of Manager and Engineer and upcast them to Employee.
- When we call the displayInfo() method on these Employee references, the actual method invoked is determined by the runtime type of the objects (Manager and Engineer), demonstrating dynamic method dispatch across different classes in the inheritance hierarchy.
Q7. What happens if a method is not overridden but is called using dynamic method dispatch?
If a method is not overridden in a subclass but is called using dynamic method dispatch in Java, the method from the superclass will be executed. The process works as follows:
- The type of reference variable is considered during compilation, but the actual type of object is checked during runtime.
- If the method is not overridden in the subclass, the JVM will invoke the superclass method as the correct version.
In conclusion, dynamic method dispatch in Java allows for maintainable code by supporting polymorphism, but it requires careful attention to performance considerations and method resolution.
Do check out the following interesting topics:
- Data Types In Java | Primitive & Non-Primitive With Code Examples
- How To Install Java For Windows, MacOS, And Linux? With Examples
- Advantages And Disadvantages of Java Programming Language
- Java Developer Resume - See How To Make ATS Catch Your Resume
- Difference Between Java And JavaScript Explained In Detail