OOP: Polymorphism

This article explains the Object Oriented Concept, Polymorphism. If you are new to Object Oriented Programming, visit to this link and get an introduction about OOP. Polymorphism is the ability of an entity to behave in different forms. Take a real world example; the Army Ants. There are different size of ants in the same ant colony with different responsibilities; workers are in different sizes and queen is the largest one. This is a polymorphic behavior where the entities have a unique feature while they share all other common attributes and behaviors.



The same concept is used in programming world where software entities need to show some different behaviors while they are in the same type and sharing all other common features. Take the bank account as an example. Current account and a savings account are two sub types of bank account and they have almost the same attributes and behaviors, but the withdrawal limit is different based on the account type. Savings account customers cannot withdraw more than their balance but the current account holders can withdraw more than their account balance. As we already covered, same attribute and behaviors of objects (technically variables and methods) can be reused among multiple classes using Inheritance as show below.
public class BankAccount {
    private String name;
    protected double balance;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void deposite(double amount) {
     this.balance += amount;
    }
   
    public boolean withdraw(double amount) {
        if (balance > amount) {
            balance -= amount;
            return true;
        } else {
            return false;
        }
    }
}
public class CurrentAccount extends BankAccount {

}
public class SavingsAccount extends BankAccount {

}
public class BankDemo {
    public static void main(String[] args) {
        CurrentAccount c = new CurrentAccount();
        SavingsAccount s = new SavingsAccount();
        c.deposite(500.00);
        s.deposite(500.00);
        doWithdrawal(c);    // Withdraw succeed.
        doWithdrawal(s);    // Withdraw failed.
    }

    public static void doWithdrawal(BankAccount acc) {
        boolean result = acc.withdraw(1000.00);
        if (result) {
            System.out.println("Withdraw succeed.");
        } else {
            System.out.println("Withdraw failed.");
        }
    }
}
For the complete project click here.

In this example, from both accounts customers cannot withdraw more than the balance. However, CurrentAccount customers must be able to withdraw successfully even if they do not have enough balance. The problem is: how we are going to implement this polymorphic behavior of bank accounts based on their type?

Java supports two types of polymorphism. The first one "Method Overriding" is used to implement the polymorphic behavior of objects. The second type "Method Overloading" is used to provide polymorphic methods based on their parameters.

Method Overriding
The bank account problem we have discussed above can be solved using method overriding. Look at this modified CurentAccount class.
public class CurrentAccount extends BankAccount {
 /**
  * Overriding method.
  */
 public boolean withdraw(double amount) {
  balance -= amount;
  return true;
 }
}
For the complete project click here.

In this class the super class's method is rewritten with a different implementation. This is known as Method Overriding. This time CurrentAccount allows to withdraw more than the available balance.
How does Java executes a method?
In Java the compiler checks the reference for any invoked methods. If the method is available in the reference, Java will compile the code without any errors. At the runtime Java executes the method from the object which is assigned to that reference. This is why we get NullPointerException at runtime if we try to access a method or variable using a null reference (A reference without an object).
In this program BankDemo’s doWithdrawal method has a reference of BankAccount and there is a method withdraw(double) in the BankAccount class. This information is enough for compilation so Java happily compiles the code. At the runtime, main method passes a CurrentAccount object first, so the withdraw method from the CurrentAccount object is invoked where we have redefined the implementation of this method. Therefore, CurrentAccount object can allow overdraft using its own withdrawal method. For SavingsAccount object the inherited method is executed and prints Withdraw failed.

In summary, Method overriding is the technique of rewriting the inherited methods in sub classes. The "inherited methods" is highlighted because we can overwrite only the inherited methods. It clearly defines another rule that is, there are no ways to overwrite private and static methods since they do not inherit to the sub classes. There are some more rules regarding method overriding.

Rule #1:
Overriding method name and the overridden method name must be exactly same.

Rule #2:
Overriding method must have same set of parameters as the overridden method.

Rule #3:
Return type of overriding method name must be same as super class’s method. From Java 1.5 version Java supports a new feature called covariant return. Covariant return allows returning a sub type of overridden method’s return type.
class ShapeFactory {
    public Shape getShape() {
        return new Shape();
    }
}

class CircleFactory extends ShapeFactory {
    public Circle getShape() {
        return new Circle();
    }
}
For the complete project click here.

Rule #4:
Access modifier of the overriding method must be same or less restrictive than the overridden method’s access modifier. For example if the super class has a method with a protected access modifier then the overriding method can have only either protected or public access modifier.

Rule #5:
Overriding method can throw new unchecked exceptions but cannot throw new checked exceptions.

Rule #6:
In Java 1.5 version, Java introduced an annotation @Override and recommends to use the annotation with the overriding method to enable type checking at compile time. Even though, it is not a strict rule to follow, developers are recommended to use @Override annotation in front of overriding methods.
public class CurrentAccount extends BankAccount {
 /**
  * Overriding method.
  */
 @Override
 public boolean withdraw(double amount) {
  balance -= amount;
  return true;
 }
}

Method Overloading
Method overloading is used to create polymorphic methods. To explain this concept lets create a Calculator class which must be able to add two long or two float values and print the output. A sample class can be something like this:
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        calc.addLong(10L, 30L);
        calc.addFloat(10.5f, 0.5f);
    }
}

class Calculator {
    public void addLong(long x, long y) {
        long result = x + y;
        System.out.println("Long addition: " + result);
    }

    public void addFloat(float x, float y) {
        float result = x + y;
        System.out.println("Float addition: " + result);
    }
}
This code seems to be fine but have you noticed that we are calling the same method System.out.println for two different data types? If Java had different println methods for different data types, like printlnByte, printlnShort, printlnInInt, etc, it would make the language more complex and developers might struggled to remember all those methods. Because of the same method name for different data types, Java reduces the complexity. This advantage is achieved by method overloading.
Method overloading defines, that you can have same method name for more than one method in the same scope until they have same list of parameters. There are no restrictions regarding the return type or modifiers of those methods. Let’s apply the method overloading to our existing Calculator class as shown below.
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        calc.add(10L, 30L);
        calc.add(10.5f, 0.5f);
    }
}

/**
 * Calculator class has two methods add(long, long) and add(float, float).
 */
class Calculator {
    public void add(long x, long y) {
        long result = x + y;
        System.out.println("Long addition: " + result);
    }

    public void add(float x, float y) {
        float result = x + y;
        System.out.println("Float addition: " + result);
    }
}
In this example there are two methods with the same method name but with different parameters. The method is selected based on the arguments passed to the method. When you call the add method with two long values, Java searches for an add method with two long parameters. Same as long, using the parameters Java selects the suitable method for float arguments. Now have a look at the following code.
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        // Passing two integer values
        calc.add(10, 30);   // add(long, long)
        // Passing two long values
        calc.add(10L, 30L); // add(long, long)
    }
}

/**
 * Calculator class has two methods add(long, long) and add(float, float).
 */
class Calculator {
    public void add(long x, long y) {
        long result = x + y;
        System.out.println("Long addition: " + result);
    }

    public void add(float x, float y) {
        float result = x + y;
        System.out.println("Float addition: " + result);
    }
}
In this example, both method calls are handled by the add(long, long) method. Executing add(long, long) method for long arguments is not a magic, but executing add(long, long) for two int arguments is something odd. The reason behind this is, implicit casting of primitive data types. As usual Java searches for an add method with two int parameters. If there is no add(int, int) method, then it searches for the next most suitable data type that is long. Since there is a method add(long, long) which can accept two int parameters, Java selects and execute that method.

Now have a look at this code where there is an add method with two int parameters.
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        // Passing two integer values
        calc.add(10, 30);   // add(int, int)
        // Passing two long values
        calc.add(10L, 30L); // add(long, long)
    }
}

/**
 * Calculator class has two methods add(long, long) and add(int, int).
 */
class Calculator {
    public void add(long x, long y) {
        long result = x + y;
        System.out.println("Long addition: " + result);
    }

    public void add(int x, int y) {
        int result = x + y;
        System.out.println("Integer addition: " + result);
    }
}
This time Java selects the add(int, int) method for int arguments, since the add(int, int) is the most suitable method than the add(long, long) method.

Conclusively, Java provides Method overriding and method overloading to achieve polymorphism. Method overriding is the technique of rewriting super class method in the sub classes. Method overriding is sometimes called as runtime method selection since the method is selected from the object at runtime. Method overloading is a technique of having more than one method with the same name but with different parameters.

Find the source codes at Git Hub.

The complete Object Oriented Programming article series is available here:
Object Oriented Programming:
            Encapsulation
            Inheritance
            Polymorphism
            Abstraction
Previous
Next Post »

Contact Form

Name

Email *

Message *