Method Overriding and Overloading in Java

Hello everyone 🀘🏼

In this article, we’ll be taking a look at the concepts of method overloading and overriding in Java, which I believe are often forgotten and confused by beginners (at least I was 😁).

Before delving into these two concepts in detail, I’d like to briefly mention them. Method overriding is simply changing the functionality of a method without changing its signature, while method overloading is redefining a method with a different signature.

These two definitions may not make much sense yet because I have only mentioned them briefly and in a simplistic manner. Although this section consists of only two sentences that may seem meaningless for now, by the end of the article, they will have the power to summarize the entire article. πŸ™‚

Before we dive into the topic fully, there is one tiny concept we need to learn and understand: The Method Signature. Method signatures consist of the name of a method and its method parameters. As shown in the image below, the signature of the method named justFun consists of its name “justFun” and its parameters int num1, int num2.

I believe we have clarified this simple and short concept. Now, let’s dive into the main concepts of this article. πŸš€

Method Overriding

Method overriding, in simple terms, refers to methods that have the same signature but perform different tasks. How does this situation arise?

Let’s assume that we have a class named Animal with a method named eat(). Now, I have created another class named Dog, and I want it to inherit from the Animal class. When we perform the inheritance process, we would like to use the eat() method inherited from the super class in a way that is suitable for our subclass. Therefore, we change its functionality. In other words, we override the eat() method.πŸ™‚

Let’s give an example!

class Animal{  
  public void eat(){System.out.println("eating...");}  
}  

//Overriding way
class Dog extends Animal{  
  public void eat(){System.out.println("eating bread...");}  
}

class Cat extends Animal{  
  public void eat(){System.out.println("eating fish...");}  
}

As we saw in the example, I did not touch the method signatures in any way. I did not change the method name or send a new parameter to that method… I just played with the internal dynamics of the method and added a completely different functionality.

Well, you may have a question like this in your mindπŸ€”:

“What about the access modifiers of the methods we override? Do they have to be the same?”

Our answer to this question will be no. They do not have to be the same, but the access modifier of the overriding method must be the same or less restrictive than the original method.

According to the restriction diagram in the image, if a protected method is to be overridden, its access modifier cannot be default or private. It can either remain protected or be made public. In short, when overriding a method, the access modifier in the subclass must be the same as or less restrictive than that in the superclass.

Another question that might come up is❓,

“Do the return types of the methods we override have to be the same? Can’t I specify a different return type?”

Our answer to this question will be No πŸ™‚. When overriding a method, the return type of the method must remain the same, along with the method signature. This is primarily based on Java’s principle of type safety.

If a subclass could use a different return type while overriding methods of the superclass, it could lead to errors in places where the superclass is used.

However, there is an exception to this rule where the return type of the overridden method can be a co-variant return type. For example, suppose the return type of a superclass is Animal, and the return type of the subclass is Cat. While overriding the superclass method, the subclass can specify the return type as Cat. This way, the object returned by the subclass will not be of type Animal, but of a more specific subtype, which is Cat.

Another question that could arise is,

“Can I override every method?”

It may be the most fundamental question to ask. πŸ™‚

Our answer to this question, unfortunately, will still be No. 😁

If the method to be overridden is:

  • final
  • static
  • private

then we cannot override these methods. This is because the nature of the override operation is based on inheritance. Methods with these three keywords cannot be inherited, so they cannot be overridden either.

However, sometimes a subclass can redefine a static method of the superclass. Some of us might have seen this situation before. This is not an override but is called Method Hiding.

If we summarize the process of overriding;

  • After the override process, the method signature remains the same (method name and parameters must stay the same), only the functionality of the method changes.

  • The return type of the overridden method should not change or it should be a co-variant return type.

  • The access modifier of the overridden method should be the same or less restrictive than the original method.

  • private, static, final methods cannot be overridden.

Now that we have gained a general understanding and logic about overriding, let’s take a look at the concept of overloading.

Method Overloading

Method Overloading is the redefinition of a method with the same name but different parameters. This means that the parameter section of the method signature is changed. This allows for the creation of different methods that perform the same task but with different parameter types, which can be called independently.

Let’s try to clarify the situation with some short code examples,

public class Calculator {

    public int summation(int num1, int num2) {
        return num1 + num2;
    }
    
    public int summation(int num1, int num2, int num3) {
        return num1 + num2 + num3;
    }
    
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        int result1 = calc.summation(2, 3);
        int result2 = calc.summation(2, 3, 4);

        System.out.println("Summation of 2 and 3: " + result1);
        System.out.println("Summation of 2, 3 and 4:  " + result2);
    }
}

In the example above, we defined two different methods with the name summation() inside the Calculator class, where the first method takes two integer parameters, and the second method takes three integer parameters. In the main method, we call both methods with different parameters and print the result to the screen.

Let’s take a look at another example to reinforce our understanding.

public class HelloWorld {
    
    public void greet() {
        System.out.println("Hello, world!");
    }
    
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
    
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.greet();
        helloWorld.greet("John");
    }
}

In the example above, the HelloWorld class defines two different methods named greet().

The first method is a simple greeting method that takes no parameters and prints the message “Hello, world!”. The second method takes a parameter of type String and uses it to create a customized greeting message. Both methods are called in the main method and their results are printed to the console.

Okey, now that we have gone through the examples, I think we have some understanding about method overloading. Let’s delve into some details about overloading using the same questions we used for understanding override.

Then our first question is as followsπŸŽ‡

“When overriding a method, we had to carefully choose the Access Modifier. That is, we had to either use the same Access Modifier as the previous method or a less restrictive one. Is this also true for Overloading?”

Our answer should be no πŸ™‚

The logic here is different from overriding. Access modifier selection was important for overriding because inheritance played a leading role. However, in overloading, we have no constraints and we are completely free to choose any modifier we want.

As in the example below,

public int sum(int a, int b, int c) {
        return a + b + c;
    }

protected void sum() {
        System.out.print("Nothing to sum");
    }

And for our another question,

“The return types of the methods we override must be the same or a co-variant type. How does this work in Overloading?”

Actually, we have already demonstrated this situation in the example we provided earlier. The return type is not important in Overloading. It can be the same or different.

“Can I overload every method?”

The answer to this question should be yes πŸ™‚ There is no problem with overloading a method that is private, static, or final.

Let’s reinforce this situation with the example below.

public class Example {
    public static void doSomething(int x) {
        System.out.println("doSomething with int: " + x);
    }
    
    public static void doSomething(double x) {
        System.out.println("doSomething with double: " + x);
    }
    
    public final void doSomething(String x) {
        System.out.println("doSomething with String: " + x);
    }
}

If we summarize the overloading process in short points;

  • After the overloading process, the method signature changes (method name remains the same, method parameters change).

  • The return type of the overloaded method can remain the same or be different.

  • The access modifier of the overloaded method can remain the same or be different. private, static, final methods can also be overloaded, there is no restriction.

If we’ve made it this far understanding, I believe we have an idea about the concepts of Overloading and Overriding. This has been a compilation of what I’ve learned and researched about Override and Overload. Thank you for taking the time to read this far.

Happy Coding! 🀞🏼