In our previous tutorial, we discussed inheritance in Java. Inheritance is one of the pillars of OOP. Inheritance allows you to ensure that all classes in a specific group under a certain supertype contain all of the methods that the supertype has. In other words, you define a common protocol for a group of classes that are related by inheritance. When we say “all the methods,” we mean “all the inheritable methods,” which for the time being actually means “all the public methods,” though we’ll refine that definition later. When you define methods that can be inherited by subclasses, in a superclass, you’re announcing a kind of protocol to other code that says, “All my subtypes (i.e. subclasses) can do these things, with these methods that look like this…”.
In other words, you establish a contract.
Class Animal establishes a common protocol for all Animal subtypes:
And keep in mind that when we say any Animal, we mean Animal as well as any class that extends from Animal. That is, any class with Animal somewhere above it in the inheritance hierarchy.
But we’re not even at the really cool part yet, because we saved the best—polymorphism—for last.
When you define a supertype for a group of classes, any subclass of that supertype can be used in place of the supertype.
What do you mean?
Don’t worry, we’re not done explaining it yet.
You’ll be an expert in a few minutes.
Polymorphism, which means “many forms,” occurs when we have many classes that are related by inheritance.
Inheritance, as mentioned in the previous tutorial, allows us to inherit attributes and methods from another class. Polymorphism employs these methods to carry out various tasks. This enables us to carry out a single action in multiple ways.
Let us give you a simple taste of polymorphism first. Suppose you have defined a class and now you want to create an object of that class type. There are 3 steps of object declaration and assignment in Java.
We will create an object of the Human type to demonstrate this.
We can write these three steps in a single line.
However, in the case of polymorphism, the reference and the object can be different.
Animal wolf = new Wolf();
The reference type can be a superclass of the actual object type. When you declare a reference variable, you can assign it to any object that passes the IS-A test for the declared type of the reference variable. In other words, the reference variable can be assigned anything that extends the declared reference variable type. This allows you to create polymorphic arrays, for example.
Animal[] animals = new Animal[5];
animals [0] = new Dog();
animals [1] = new Cat();
animals [2] = new Wolf();
animals [3] = new Hippo();
animals [4] = new Lion();
for (int i = 0; i < animals.length; i++) {
animals[i].eat();
animals[i].roam();
}
On top of that, you can have polymorphic arguments and return types. We will discuss it in the next section of this tutorial when we discuss method overloading.
For now, keep in mind that Polymorphism allows you to write code that does not need to change when new subclass types are introduced into the program.
There are two types of polymorphism in Java.
Before getting into overloading and overriding, let me explain what is run time and what is compile time.
Like our previous tutorials let us add a scenario to explain Polymorphism.
Suppose Your school is hosting a cricket tournament. Players in that tournament must be assembled into teams. You're a die-hard fan of cricket. You will now form a team with the assistance of your friends. Consider cricket as a potential project. Before entering into runtime (ground), your projects require multiple methods, such as four bowler methods, four batsman methods, three all-rounder methods (including you), and two bat methods. Now that you've formed a team and are in the process of compiling and executing the project, you're ready to compete with other teams. In other words, you are in the ground to play with other teams. Before entering the ground, the compiler (tournament authorized person) will check to see if you have 11 people and 2 bats. It will also look for alternative methods (compile time polymorphism -overloading). The compiler will thoroughly check your team before it enters runtime, in accordance with the tournament rules and regulations (tournament authorized person). Assume the compiler does not check whether or not kidney pads were given to players, but the compiler does check for alternatives (compile time polymorphism -overloading). Assume your team has entered into the ground and your program is up and running. However, in the middle of the game one of your bowlers is injured while fielding. In our Java terms one of the methods may not function anymore. But you do not have to worry. There is a compile time polymorphism -overloaded alternative available. Instead, you can use that method. One of your team's bats broke in the middle of the game (runtime). What a stroke of bad luck. This scenario has not been tested by the compiler. Assume one of your team members has an extra bat that is now available at run time. This method (3rd bat) is known as Run time polymorphism - overriding. Your good fortune takes precedence. You can now happily run your project with the available resources.
Exceptions are typically discovered by the compiler prior to runtime. Never will a piece of code reach runtime without first passing through the compiler (in java). As a result, almost all code has been validated.
Overloading
It’s very straightforward. In simple words, Within the same class, you would have two or more methods with the same name but passing different parameters. These methods are then referred to as overloading methods.
Method overloading is simply the existence of two methods with the same name but different argument lists. Period. Overloaded methods do not involve polymorphism!
With method overloading, multiple methods can have the same name with different parameters:
int myMethod(int x)
float myMethod(float x)
double myMethod(double x, double y)
Oh right, it can also have different return types.
Overloading allows you to create multiple versions of a method with different argument lists for the convenience of the callers. For example, if you have a method that only accepts ints, the calling code must convert, say, a double into an int before calling your method. However, if you overloaded the method with another version that accepts a double, you’ve made things easier for the caller. More on this when we look at constructors in the object lifecycle tutorial.
Overloaded methods have much more flexibility because they are not attempting to fulfill the polymorphism contract defined by their superclass. It has nothing to do with inheritance and polymorphism.
You have to remember the following things while trying to overload a method.
The return type can be different
You may change the return types in overloaded methods as long as the argument lists are different.
You can’t change ONLY the return type.
If only the return type differs, the overload is invalid; the compiler will assume you’re attempting to override the method. Even then, the return type must be a subtype of the return type declared in the superclass. To overload a method, you must change the argument list, but the return type can be anything.
You can vary the access levels in any direction.
You may overload a method with a more restrictive method. It makes no difference because the new method is not obligated to fulfill the contract of the overloaded method.
public class Overloads {
String uniqueID;
public int addNums(int a, int b) {
return a + b;
}
public double addNums(double a, double b) {
return a + b;
}
public void setUniqueID(String theID) {
// lots of validation code, and then:
uniqueID = theID;
}
public void setUniqueID(int ssNumber) {
String numString = “” + ssNumber;
setUniqueID(numString);
}
}
Overriding
We have already discussed it in the previous tutorial. Nevertheless, let us discuss it again.
You agree to fulfill the contract when you override a method from a superclass. The agreement states, for example, “I accept no arguments and return a boolean.” In other words, the arguments and return types of your overriding method must look exactly like the overridden method in the superclass to the outside world.
The methods are the contract.
Arguments must be the same, and return types must be compatible.
The superclass contract specifies how other codes can use a method.
Whatever argument the superclass accepts, the subclass overriding the method must use the same argument. Furthermore, whatever the superclass declares as a return type, the overriding method must declare the same type or a subclass type. Remember that a subclass object is guaranteed to be able to do anything its superclass declares, to return a subclass where the superclass is expected to be safe.
The method can’t be less accessible.
That means the level of access must be the same or friendlier. That means you can’t, for example, make a public method private by overriding it. What a surprise it would be for the code invoking what it believes is a public method (at compile time) if the JVM slammed the door shut at runtime because the overriding version called at runtime is private!
We’ve learned about two types of access so far: private and public.
The other rule about overriding is related to exception handling, but we’ll cover that in the exceptions.