Complete Guide to Method Overriding in Java
Method Overriding
Method overriding, also known as run-time polymorphism, is used to override methods of a parent class. This allows the child class to provide a specific implementation for a method that is already defined in its parent class. By doing so, it gives the child class the special ability to modify the behavior of the parent class's methods.
Method overriding can be done using the @Override
annotation. This annotation helps the Java compiler understand that the specific method in the child class is an overridden method. It allows the child class to modify the method's body, providing a different implementation than that of the parent class's method.
Syntax for Method Overriding
class ParentClass {
// Parent class method
void display() {
// Method implementation
System.out.println("Inside Parent Class Display");
}
}
class ChildClass extends ParentClass {
// Overriding the parent class method
@Override
void display() {
// Modified method implementation
System.out.println("Inside Child Class Display");
}
}
Here, ParentClass
acts as a parent class that has a method named display()
, and ChildClass
is a child class of the ParentClass
(the extends
keyword is used to express their parent-child relationship). This ChildClass
has an overridden method display()
, where you can modify the method's body according to the child class's requirements.
Note: To perform method overriding, both the parent class method and the child class method should have the same name. If both the methods have dissimilar names then you can see an error message while using @override
annotation.
This example will clearly show you the error message that appears if you change the method's name in the child class.
class ParentClass {
// Parent class method
void display() {
System.out.println("This is the display method of ParentClass.");
}
}
class ChildClass extends ParentClass {
// Incorrect method name, should be 'display'
@Override
void show() {
System.out.println("This is the show method of ChildClass.");
}
}
When you try to compile this code, you will get an error message similar to the following:
ChildClass.java:7: error: method does not override or implement a method from a supertype
@Override
^
1 error
Note: Private methods can never be overriden in java
class ParentClass {
// Private method in the parent class
private void display() {
System.out.println("This is the display method of ParentClass.");
}
}
class ChildClass extends ParentClass {
// Attempt to override the private method
@Override
private void display() {
System.out.println("This is the display method of ChildClass.");
}
}
When you try to compile this code, you will get an error message similar to the following:
ChildClass.java:7: error: method does not override or implement a method from a supertype
@Override
^
1 error
This error occurs because the display
method in ParentClass
is private and cannot be overridden in ChildClass
. In real life, a master chef would never share his recipe with outsiders.
Dynamic Polymorphism
Dynamic Polymorphism, also known as runtime polymorphism, is achieved through method overriding. This occurs when a subclass provides a specific implementation of a method already provided by its superclass.
Consider a score-generating application like ESPN, which provides scorecards for different games. The basic functionality of updating a scorecard is common, but the specifics may vary based on the game. For instance, a Football class should update football scores, and a Cricket class should update cricket scores.
// Parent class
class ScoreGenerator {
void updateScore() {
System.out.println("Updating score...");
}
}
// Child class for Football
class FootballScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating football score...");
// Specific implementation for football scores
}
}
// Child class for Cricket
class CricketScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating cricket score...");
// Specific implementation for cricket scores
}
}
// Child class for Hockey
class HockeyScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating hockey score...");
// Specific implementation for hockey scores
}
}
In this example:
ScoreGenerator
is the parent class with a methodupdateScore()
that prints a generic message.FootballScore
,CricketScore
, andHockeyScore
are child classes that extendScoreGenerator
and override theupdateScore()
method to provide specific implementations for football, cricket, and hockey scores respectively.
Each child class (FootballScore
, CricketScore
, HockeyScore
) inherits the updateScore()
method from the ScoreGenerator
parent class but provides its own unique implementation suitable for updating scores specific to football, cricket, and hockey.
Example for Method Overriding
Here's an example that illustrates the concept of method overriding:
// Parent class
class ScoreGenerator {
void updateScore() {
System.out.println("Updating score...");
}
}
// Child class for Football
class FootballScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating football score...");
// Specific implementation for football scores
}
}
// Child class for Cricket
class CricketScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating cricket score...");
// Specific implementation for cricket scores
}
}
// Child class for Hockey
class HockeyScore extends ScoreGenerator {
@Override
void updateScore() {
System.out.println("Updating hockey score...");
// Specific implementation for hockey scores
}
}
// Main class to demonstrate usage
public class Main {
public static void main(String[] args) {
ScoreGenerator footballScore = new FootballScore();
footballScore.updateScore(); // Calls FootballScore's updateScore method
ScoreGenerator cricketScore = new CricketScore();
cricketScore.updateScore(); // Calls CricketScore's updateScore method
ScoreGenerator hockeyScore = new HockeyScore();
hockeyScore.updateScore(); // Calls HockeyScore's updateScore method
}
}
In this example:
ScoreGenerator: This is the parent class that defines a method
updateScore()
. The methodupdateScore()
is implemented to print a generic message"Updating score..."
.FootballScore, CricketScore, HockeyScore: These are child classes of
ScoreGenerator
. Each class extendsScoreGenerator
and overrides theupdateScore()
method with its own specific implementation:FootballScore: Overrides
updateScore()
to print"Updating football score..."
.CricketScore: Overrides
updateScore()
to print"Updating cricket score..."
.HockeyScore: Overrides
updateScore()
to print"Updating hockey score..."
.Each overridden method could contain additional logic specific to updating scores for football, cricket, or hockey, respectively.
Main Class:
Dynamic Polymorphism: In the
main
method, we create instances of each child class (FootballScore
,CricketScore
,HockeyScore
) and assign them toScoreGenerator
references (footballScore
,cricketScore
,hockeyScore
).Each time
updateScore()
is called on these references, Java uses dynamic method dispatch to call the overriddenupdateScore()
method specific to the actual object type (FootballScore
,CricketScore
,HockeyScore
).Therefore,
footballScore.updateScore()
callsFootballScore
'supdateScore()
method,cricketScore.updateScore()
callsCricketScore
'supdateScore()
method, andhockeyScore.updateScore()
callsHockeyScore
'supdateScore()
method.
Rules For Method Overriding
Rule 1: The access modifier type specified in the parent class must match that of the overridden method in the child class, otherwise you will encounter an error.
// Parent class
class Parent {
protected void display() {
System.out.println("Inside parent class display");
}
}
// Child class extending Parent
class Child extends Parent {
protected void display() {
System.out.println("Inside child class display");
}
}
// Main class to demonstrate method invocation
public class App {
public static void main(String[] args) {
Child ch = new Child();
ch.display(); // Calls Child's display method
}
}
In order to perform method overriding in Java, both the parent class method and the child class method must have the same access modifier.
In the given example:
The
Parent
class defines aprotected
methoddisplay()
that prints"Inside parent class display"
.The
Child
class extendsParent
and overrides thedisplay()
method with its ownprotected
version that prints"Inside child class display"
.
In the App
class:
An instance of
Child
(ch
) is created.When
ch.display()
is called, it invokes thedisplay()
method fromChild
, demonstrating method overriding where the behavior of the method is based on the type of the actual object (Child
) at runtime.
Rule 2: In method overriding, the visibility or access modifiers of the parent class method and the overridden method in the child class can be different, provided that the visibility of the overridden method in the child class is the same or more accessible than that of the parent class method.
If the visibility is decreased (i.e., made more restrictive) in the child class, it will result in a compile-time error.
// Parent class
class Parent {
protected void display() {
System.out.println("Inside parent class display");
}
}
// Child class extending Parent
class Child extends Parent {
// Attempting to reduce visibility (compile-time error)
private void display() { // error
System.out.println("Inside child class display");
}
}
// Main class to demonstrate method invocation
public class App {
public static void main(String[] args) {
Child ch = new Child();
ch.display(); // Compilation error: Attempting to assign weaker access privileges; was protected
}
}
The Child
class attempts to override display()
with private
access, which is more restrictive than protected
. This violates the rule that the visibility of the overriding method must be the same or broader than that of the overridden method. Therefore, the Java compiler generates a compilation error indicating that it is "Attempting to assign weaker access privileges".
To correct this, the access modifier of display()
in the Child
class should either match (protected
) or widen (public
) the access modifier of display()
in the Parent
class to properly override the method without errors.
Note:
If the parent class method is
public
, the child class overridden method can also bepublic
.If the parent class method is
protected
, the child class overridden method can beprotected
orpublic
.If the parent class method is default (package-private), the child class overridden method can be default,
protected
, orpublic
; it can never beprivate
.
Rule 3: The return type of both the parent class method and the child class method should be the same; if they are different, method overriding is not allowed.
Example
// Parent class
class Parent {
int display() {
System.out.println("Inside parent class display");
return 10;
}
}
// First child class extending Parent
class Child1 extends Parent {
// Error: Attempting to change return type to float
@Override
public float display() {
System.out.println("Inside Child1 class display");
return 1.5f; // Compile-time error: Return type mismatch
}
}
// Second child class extending Parent
class Child2 extends Parent {
// Correctly overriding with the same return type
@Override
int display() {
System.out.println("Inside Child2 class display");
return 4;
}
}
// Main class to demonstrate method invocation
public class Main {
public static void main(String[] args) {
Parent obj1 = new Child1();
obj1.display(); // Error at compile time due to return type mismatch
Parent obj2 = new Child2();
obj2.display(); // Calls Child2's display method
}
}
The program illustrates the rule that in method overriding, the return type of the overridden method in the child class must be the same as (or covariant with) the return type of the method in the parent class. Attempting to change the return type in the child class (Child1
) results in a compilation error, while correctly matching the return type (Child2
) allows for successful method overriding.
Rule 4: n method overriding, the parent class method and the child overridden method can have different return types, but they must be co-variant return types, meaning there should be a parent-child relationship between them.
// Parent class representing electronic devices
class ElectronicDevice {
}
// Subclass extending ElectronicDevice, representing a specific type of electronic device
class Laptop extends ElectronicDevice {
}
// Parent class with a method to display electronic devices
class Parent {
// Method in Parent class to display an ElectronicDevice
ElectronicDevice display() {
System.out.println("Parent class display");
return new ElectronicDevice();
}
}
// Child class extending Parent, overriding the display method
class Child1 extends Parent {
// Override of display method in Child1
@Override
ElectronicDevice display() {
System.out.println("Child1 class display");
return new ElectronicDevice();
}
}
// Another Child class extending Parent, with a different override of the display method
class Child2 extends Parent {
// Override of display method in Child2, returning a Laptop
@Override
ElectronicDevice display() {
System.out.println("Child2 class display");
return new Laptop();
}
}
// Main class to demonstrate method invocation and polymorphism
public class Main {
public static void main(String[] args) {
// Creating an instance of Child1 and invoking display method
Parent obj1 = new Child1();
obj1.display(); // Calls Child1's display method
// Creating an instance of Child2 and invoking display method
Parent obj2 = new Child2();
obj2.display(); // Calls Child2's display method
}
}
Covariant return types in Java allow a method in a subclass to return a more specific type than the method in its superclass. In our example:
The Parent class defines
display()
returningElectronicDevice
.Child1 and Child2 override
display()
returningElectronicDevice
andLaptop
respectively, maintaining a hierarchical relationship whereLaptop
is a subtype ofElectronicDevice
. This is an example of covariant return types in Java.
Advantages of Method Overriding
Polymorphism: It allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This means that a method can behave differently based on the object that invokes it, enabling polymorphic behavior.
Code Reusability: By overriding methods, subclasses can reuse code defined in their superclasses. There is no need to re write those lines. This promotes code reuse and reduces redundancy in software development. This helps us to overcome code redundancy.
Enhanced Readability: Method overriding makes code easier to understand by letting developers use clear method names in subclasses that show exactly what each method does, making the codebase clearer overall.
Flexibility: Method overriding allows subclasses to adapt inherited methods to meet their specific needs without directly altering the superclass code, thus offering greater flexibility in class design and behavior customization.
Conclusion
In this blog, we've explored method overriding in Java, where a subclass can customize the behavior of a method inherited from its superclass.
Throughout our discussion, we've emphasized key rules for method overriding: ensuring method signatures match, using appropriate access modifiers, and respecting return type covariance. These rules help subclasses extend and adjust inherited functionality without disrupting the class hierarchy.
Method overriding promotes code reusability by reducing redundancy, improves code readability with clear method naming, and offers flexibility in designing class behaviors. Following these principles enables developers to create robust Java applications that capitalize on object-oriented programming and polymorphism.
In summary, method overriding in Java is essential for scalable and adaptable software development. It empowers developers to efficiently manage code and expand class functionalities while maintaining structure and ease of use.
Subscribe to my newsletter
Read articles from Sridhar K directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sridhar K
Sridhar K
I am a fresher Software developer with a strong foundation in Java, HTML, CSS, and JavaScript. I have experience with various libraries and frameworks, including Spring Boot, Express.js, Node.js, Hibernate, MySQL, and PostgreSQL. I am eager to leverage my technical skills and knowledge to contribute effectively to a dynamic development team. I am committed to continuous learning and am excited to begin my career in a challenging developer role.