A method in Java is a function that defines what an object of a class can do. One of the main tasks of a method is to perform actions on the object's data. They can change values, transform data, and print to the console.
Methods can be overloaded and overridden. In this article, we'll explore how to do this and the differences between these two mechanisms.
Method overloading in Java means using one method name with different parameters. To understand this mechanism, let's start with a simple example - creating a helper to greet users.
public class Assistant {
public void sayHello(String name) {
System.out.println("Good day, " + name + "!");
}
public static void main(String[] args) {
Assistant assistant = new Assistant();
assistant.sayHello("Michael");
}
}
The console will display the phrase "Good day, Michael!".
Suppose Michael didn't come alone but with his friend Victor. The current implementation of the method greets only Michael and ignores Victor. To fix this, we'll implement two methods in the class with the same name but different parameters.
public class Assistant {
public void sayHello(String firstGuest) {
System.out.println("Good evening, " + firstGuest + "!");
}
public void sayHello(String firstGuest, String secondGuest) {
System.out.println("Good day, " + firstGuest + " and " + secondGuest + "!");
}
public static void main(String[] args) {
Assistant assistant = new Assistant();
assistant.sayHello("Michael", "Victor");
}
}
Now the console will display "Good day, Michael and Victor!".
We have overloaded sayHello()
. The program is now more flexible - the assistant can greet two guests at once. But what happens if three, four, or five guests arrive? Let's check:
public class Assistant {
public void sayHello(String firstGuest) {
System.out.println("Good evening, " + firstGuest + "!");
}
public void sayHello(String firstGuest, String secondGuest) {
System.out.println("Good day, " + firstGuest + " and " + secondGuest + "!");
}
public static void main(String[] args) {
Assistant assistant = new Assistant();
assistant.sayHello("Michael", "Victor", "Mary");
}
}
We will get an error because sayHello()
is ready to accept only two arguments. A straightforward solution is to continue overloading it to accept three, four, five, or more guests. However, this isn't flexible programming. We would constantly need to add more code. We would constantly need to add more code.
A more flexible solution is to pass a variable-length argument (String... names). This allows sayHello()
to accept any number of strings. To print a greeting for each guest to the console, we can use a loop.
public class Assistant {
public void sayHello(String firstGuest) {
System.out.println("Good evening, " + firstGuest + "!");
}
public void sayHello(String... names) {
for (String name : names) {
System.out.println("Good evening, " + name + "!");
}
}
public static void main(String[] args) {
Assistant assistant = new Assistant();
assistant.sayHello("Michael", "Victor", "Mary", "Andrew", "Anna");
}
}
The console will display a greeting for each guest:
Good evening, Michael!
Good evening, Victor!
Good evening, Mary!
Good evening, Andrew!
Good evening, Anna!
Managed solution for Backend development
In the example above, we didn't think about the order of arguments because they were all strings. It doesn't matter whether we greet Michael or Anna first.
But the order of arguments matters if the method takes, for example, a string and a number. Look at this:
public class User {
public static void sayYourAge(String greeting, int age) {
System.out.println(greeting + " " + age);
}
public static void main(String[] args) {
sayYourAge(20, "My age is - "); //error!
}
}
A compilation error occurs because we defined sayYourAge()
to accept a string first and then a number, but we passed the arguments in the opposite order.
To fix the error, pass the arguments in the correct order:
public class User {
public static void sayYourAge(String greeting, int age) {
System.out.println(greeting + " " + age);
}
public static void main(String[] args) {
sayYourAge("My age is - ", 20);
}
}
To avoid errors, you can overload by the order of parameters. For example:
public class User {
public static void sayYourAge(String greeting, int age) {
System.out.println(greeting + " " + age);
}
public static void sayYourAge(int age, String greeting) {
System.out.println(greeting + " " + age);
}
public static void main(String[] args) {
sayYourAge("My age is - ", 20);
sayYourAge(20, "My age is - ");
}
}
Now it doesn't matter in which order the arguments are passed—the program will understand both options.
From the examples above, we can identify three variations of overloading Java methods.
By the number of parameters:
public class Calculator {
void calculate(int number1, int number2) { }
void calculate(int number1, int number2, int number3) { }
}
By parameter types:
public class Calculator {
void calculate(int number1, int number2) { }
void calculate(double number1, double number2) { }
}
By parameter order:
public class Calculator {
void calculate(double number1, int number2) { }
void calculate(int number1, double number2) { }
}
Finally, let's reiterate what method overloading in Java means. It is a language mechanism that allows the creation of multiple methods with the same name but different parameters. This is not possible in all languages.
Overloading is part of polymorphism, one of the key components of object-oriented programming. The main advantage of overloading in Java is the ability to use similar methods with the same names.
Method overriding in Java allows you to take a method from a parent class and create a specific implementation in a subclass.
To understand this better, let's look at an example. Suppose you create a class Animal
with a method voice()
. It allows the animal to make a sound:
public class Animal {
public void voice() {
System.out.println("Speak!");
}
}
The problem immediately arises - all animals make different sounds. You could create a separate method for each. For example, for a cat, it would be voiceCat()
, and for a dog, it would be voiceDog()
. But imagine how many lines of code you would have to write to allow all animals to make a sound.
This is where the overriding mechanism in Java comes in handy. It allows you to replace the implementation in the subclass. Let's look at examples with a cat and a dog:
public class Cat extends Animal {
@Override
public void voice() {
System.out.println("Meow!");
}
}
public class Dog extends Animal {
@Override
public void voice() {
System.out.println("Woof!");
}
}
The output will first display "Meow" and then "Woof". To achieve this result, you need to:
Create a method in the subclass with the same name as in the parent class.
Add the @Override
annotation before it. This annotation tells the compiler it is not a mistake; you intentionally override the method. Note that the annotation is not mandatory. Creating a method in the subclass with the same signature as the parent class will still override the method. However, it is recommended that you always use the annotation, as it improves code readability and ensures the compiler checks at compile time that such a method exists in the parent class.
You write a custom implementation for each subclass. If you don't do this, the parent class's implementation will be used. Even after overriding, you can refer to the parent class's method as long as it is not defined with the private modifier. To do this, use the super
keyword:
super.method();
Simple and fully managed app deployment
Overriding class methods in Java has several restrictions.
The method name must be identical to the parent's method (i.e., the method signature must be identical).
The arguments must remain the same as the parent's method.
The return type must be the same as the parent's method.
The access modifier must be the same as the parent's method.
Final methods cannot be overridden. One way to prevent overriding is to declare the method using the final
keyword. For example:
class Parent {
final void show() {}
}
class Child extends Parent {
void show() {} // Error!
}
This code will return an error because the parent class used the final
keyword.
Static methods cannot be overridden. If you define the same method signature in the subclass as in the parent class, you will perform hiding. More details can be found in the documentation.
Private methods cannot be overridden as they are bound at compile time, not runtime.
You cannot narrow the access modifier, for example, from public
to private
. Widening the access level is possible.
You cannot change the return type, but you can narrow it if they are compatible.
Overriding a method in Java adheres to the rules listed above, which must be followed.
Some methods have their own overriding rules, such as equals()
and hashCode()
. The most important condition is that if you override equals()
, you must also override hashCode()
. Otherwise, classes and methods that rely on the contracts of these two methods' standard implementation might work incorrectly. We discussed this in detail in another article.
Method overriding and overloading in Java are important parts of polymorphism but are different mechanisms. When overloading, you create multiple methods within one class with the same name but different parameters. When overriding, you take the same method and make it do different things depending on which class it is called in.
However, Java overloading and overriding share some characteristics. Both mechanisms help make code cleaner and more readable and reduce the number of errors during program execution.