Java method overriding

Java follows the object-oriented paradigm, which includes features like method overriding. In this tutorial, I’ll explain how Java handles method overriding.

Ivan Polovyi
Javarevisited
8 min readJun 2, 2024

--

Let’s first clarify what method overriding is. Method overriding occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. The method in the subclass must have the same name, return type, and parameters as the method in the superclass. This feature enables runtime polymorphism, allowing a subclass to offer specific behavior while using a common interface.

Example

The theory is good, but a theory with a practical example is even better. To explain method overriding, I will use the following class. This is a diagram:

And the class looks like this:

package com.polovyi.ivan.tutorials;

class Animal {

private String name;
private int age;

public Animal(String name, int age) {
this.name = name;
this.age = age;
}

public void displayDetails() {
System.out.println("Animal Name: " + name +
", Age: " + age);
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

This is a very simple class. It has a couple of fields and their accessor methods, one constructor, and one public method. Let’s create an object of this class and call it its instance method.

package com.polovyi.ivan.tutorials;

public class Examples {

public static void main(String[] args) {
Animal animal = new Animal("Animal", 1);
animal.displayDetails();
}
}
// Output: Animal Name: Animal, Age: 1

Instance Method

Let’s create another class and extend the animal class.

class Mammal extends Animal {

private boolean hasFur;

public Mammal(String name, int age, boolean hasFur) {
super(name, age);
this.hasFur = hasFur;
}

public boolean hasFur() {
return hasFur;
}
}

Now, we create an object of this new class and call the same method.

package com.polovyi.ivan.tutorials;

public class Examples {

public static void main(String[] args) {
Mammal mammal = new Mammal("Mammal", 2, true);
mammal.displayDetails();
}
}
// Animal Name: Mammal, Age: 2

This call invoked the parent class method. The return is not what one may expect because the method prints values of fields from the parent class. But the child class has an extra field hasFur. Obviously, the parent class does not know about the existence of this field.

We have to use the same method to display this extra field but inside the child class.

package com.polovyi.ivan.tutorials;

class Mammal extends Animal {

private boolean hasFur;

public Mammal(String name, int age, boolean hasFur) {
super(name, age); // Call to the superclass constructor
this.hasFur = hasFur;
}

@Override
public void displayDetails() {
System.out.println("Mammal Name: " + getName() + ", "
+ "Age: " + getAge() +
", Has Fur: " + hasFur);
}

public boolean hasFur() {
return hasFur;
}
}

Now, when we run the code again, we will see the following output:

package com.polovyi.ivan.tutorials;

public class Examples {

public static void main(String[] args) {
Mammal mammal = new Mammal("Mammal", 2, true);
mammal.displayDetails();
Animal animalMammal = new Mammal("AnimalMammal", 2, true);
animalMammal.displayDetails();
}
}
// Mammal Name: Mammal, Age: 2, Has Fur: true
// Mammal Name: AnimalMammal, Age: 2, Has Fur: true

As you can see at the run time, the method of the class that the object was made of was called. In this example, I create two objects and reference them with parents and a chilled class. But the method, in both cases, was invoked from the class of the object, not the class of reference.

There are a couple of rules that have to be followed during the overriding of a method:

Method signature

The method in the subclass must keep the same method signature as a method it overrides. Hence, the method name, number of parameters, and their type and order must be the same.

Final methods

Final methods cannot be overridden.

Private methods

Private methods cannot be overridden because they are accessible only from the class where they are declared.

Method accessibility

The method in the subclass can’t narrow the accessibility of the method in a parent class, but it can widen it.

Let’s add the following method to the Animal class:

 protected void displayDetailsProtected() {
System.out.println("Animal Name: " + name +
", Age: " + age);
}

It can be overridden in the Animal class like so:

@Override
public void displayDetailsProtected() {
System.out.println("Mammal Name: " + getName() + ", "
+ "Age: " + getAge() +
", Has Fur: " + hasFur);
}

But we can’t override it like this:

// Won't compile
// @Override
// void displayDetailsProtected() {
// System.out.println("Mammal Name: " + getName() + ", "
// + "Age: " + getAge() +
// ", Has Fur: " + hasFur);
// }

// Won't compile
// @Override
// private void displayDetailsProtected() {
// System.out.println("Mammal Name: " + getName() + ", "
// + "Age: " + getAge() +
// ", Has Fur: " + hasFur);
// }

Return type

The method's return type in the subclass can be the same or a subtype of a parent class method.

Let’s add this method to an Animal class:

public Number getAgeAsNumber() {
return age;
}

This method will return the age of an animal not as an integer but as a Number. This method can be overridden like this:

@Override
public Integer getAgeAsNumber() {
return super.getAgeAsNumber().intValue();
}

This is called covariant return, and it only applies to reference types, not primitive types. However, we cannot override a method with a return type of Integer with a method with a return type of Number.

Note how I call a parent class method from within a child method using the super keyword. Because both methods have the same name and we can call both methods (parent and child) from a child class, we have to add this keyword so the compiler knows which method will be invoked.

Exceptions

The method in the parent class may specify an exception or a list of exceptions after the throws clause. A method in the subclass can throw either all or none or a subset of checked exceptions included in their subclasses. The overriding method can throw any unchecked exceptions.

Let’s say we add this method to a parent class that throws a checked exception:

    public void displayDetailsWithException() throws IOException {
System.out.println("Animal Name: " + name +
", Age: " + age);
}

The FileSystemException is a checked exception and has the following inheritance.

We can override this method either with IOException or FileSystemException.

@Override
public void displayDetailsWithException() throws FileSystemException {
System.out.println("Animal Name: " + getName() +
", Age: " + getAge());
}

But this method can’t throw an Exception. So, this wouldn’t work because it is a parent type and not a subtype or a type itself.

//  Won't compile
// @Override
// public void displayDetailsWithException() throws Exception {
// System.out.println("Animal Name: " + getName() +
// ", Age: " + getAge());
// }

We can add more exceptions like so:

@Override
public void displayDetailsWithException() throws FileSystemException, IOException {
System.out.println("Animal Name: " + getName() +
", Age: " + getAge());
}

But we can’t do something like this:

//  Won't compile
// @Override
// public void displayDetailsWithException() throws FileSystemException, Exception {
// System.out.println("Animal Name: " + getName() +
// ", Age: " + getAge());
// }

Because in this example, we added a new class that is not in the throws of a parent class nor a subclass of the exception present in the throw in the parent class.

Let’s add to the parent class a method with multiple exceptions:

public void displayDetailsWithMultipleException() throws IOException, ClassNotFoundException {
System.out.println("Animal Name: " + name +
", Age: " + age);
}

We can override this method in a child class with only one exception.

@Override
public void displayDetailsWithMultipleException() throws ClassNotFoundException {
System.out.println("Animal Name: " + getName() +
", Age: " + getAge());
}

or even without exceptions:

@Override
public void displayDetailsWithMultipleException() {
System.out.println("Animal Name: " + getName() +
", Age: " + getAge());
}

Unlike checked exceptions, unchecked exceptions can be added to a subclass method.

@Override
public void displayDetailsWithMultipleException() throws
ClassNotFoundException,
RuntimeException {
System.out.println("Animal Name: " + getName() +
", Age: " + getAge());
}

Bellow rules also apply to interfaces where chilled interfaces override abstract and defouled methods from their parent interfaces.

Override annotation

The @Override annotation in Java is used to indicate that a method in a subclass is intended to override a method in its superclass. It helps ensure that the method signature in the subclass matches that of the superclass, preventing errors and improving code clarity. While optional, using @Override is recommended as it assists the compiler in verifying the method's existence in the superclass and generates an error if no matching method is found. Additionally, it enhances code readability and maintainability.

Static methods

Static methods cannot be overridden by an instance method because a static method is attached to a class, while an instance method is attached to an object. We cannot override a static method with another static method. Instead, we can hide a static method in the parent class by declaring a static method in the child class with the same signature, which is known as method hiding.

Let's define the following method in a parent class:

public static void displayDetailsStatic() {
System.out.println("Static method from Animal Class");
}

We can hide it in a child class, like so:

public static void displayDetailsStatic() {
System.out.println("Static method from Mammal Class");
}

We can run the code and check the output:

package com.polovyi.ivan.tutorials;

public class Examples {

public static void main(String[] args) {
Animal animal = new Animal("Animal", 1);
Mammal mammal = new Mammal("Mammal", 2, true);
Animal animalMammal = new Mammal("AnimalMammal", 2, true);
mammal.displayDetailsStatic();
animal.displayDetailsStatic();
animalMammal.displayDetailsStatic();

}
}
// Static method from Mammal Class
// Static method from Animal Class
// Static method from Animal Class

One important point to note is that the subclass does not inherit methods overridden in the superclass.

The complete project can be found here:

Conclusion

Method overriding in Java may seem confusing at first glance, but it becomes clear and straightforward with a detailed explanation. In this tutorial, I explained how to use method overriding effectively, using practical examples to illustrate its concepts.

Thank you for reading! If you enjoyed this post, please like and follow it. If you have any questions or suggestions, feel free to leave a comment or connect with me on my LinkedIn account.

--

--

Ivan Polovyi
Javarevisited

I am a Java Developer | OCA Java EE 8 | Spring Professional | AWS CDA | CKA | DCA | Oracle DB CA. I started programming at age 34 and still learning.