Java Polymorphism
Java Polymorphism
Section titled “Java Polymorphism”Polymorphism, meaning “many forms”, is a core OOP concept. It allows objects of different classes to be treated as objects of a common superclass or interface. The most common use is when a superclass reference variable refers to an object of one of its subclasses.
An object is polymorphic if it passes more than one IS-A test (determined by inheritance and interface implementation). In Java, every object is inherently polymorphic because it IS-A itself and it IS-A Object.
Reference Variables and Objects:
Section titled “Reference Variables and Objects:”Key points to remember:
- Objects are accessed via reference variables.
- A reference variable has a declared type (class or interface) that cannot change.
- The type of the reference variable determines which methods can be called at compile time.
- A reference variable can refer to an object of its declared type or any subtype (subclass or implementing class) of its declared type.
- A single object in memory can be referred to by multiple reference variables of different compatible types.
Example: IS-A Relationships
Section titled “Example: IS-A Relationships”Consider these definitions:
interface Vegetarian {}class Animal {}class Deer extends Animal implements Vegetarian {}A Deer object exhibits polymorphism:
- A
DeerIS-AAnimal(due toextends) - A
DeerIS-AVegetarian(due toimplements) - A
DeerIS-ADeer(its own type) - A
DeerIS-AObject(implicitly)
Therefore, the following assignments are valid:
// Create a Deer objectDeer deerObj = new Deer();
// Assign the Deer object to references of its supertypesAnimal animalRef = deerObj; // OK: Deer IS-A AnimalVegetarian vegetarianRef = deerObj; // OK: Deer IS-A VegetarianObject objectRef = deerObj; // OK: Deer IS-A ObjectDeer deerRef = deerObj; // OK: Deer IS-A Deer
// All references (deerRef, animalRef, vegetarianRef, objectRef) point to the SAME Deer object in memory.Method Invocation and Virtual Methods
Section titled “Method Invocation and Virtual Methods”Polymorphism shines when combined with method overriding. When a subclass provides its own specific implementation of a method inherited from a superclass, it overrides the superclass method.
Java uses virtual method invocation (also called dynamic method dispatch or late binding). This means:
- Compile Time: The compiler checks if a method call is valid based on the reference variable’s type. (e.g.,
animalRef.eat()is valid only if theAnimalclass has aneat()method). - Run Time: The JVM determines which actual method implementation to execute based on the actual object’s type that the reference points to at that moment. If the actual object is a
Deer, theDeer’s version of the method is called, even if the reference is of typeAnimal.
All non-static, non-final, non-private methods in Java are implicitly virtual.
Example: Employee Mail Check
Section titled “Example: Employee Mail Check”Consider an Employee superclass and a Salary subclass that overrides the mailCheck method.
/* File: Employee.java */public class Employee { private String name; private String address; private int number;
public Employee(String name, String address, int number) { System.out.println("Constructing an Employee"); this.name = name; this.address = address; this.number = number; }
// Base mailCheck implementation public void mailCheck() { System.out.println("Mailing check to " + this.name + " at " + this.address); }
@Override public String toString() { return name + " " + address + " " + number; }
// Getters and Setters omitted for brevity... public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; }}/* File: Salary.java */public class Salary extends Employee { private double salary; // Annual salary
public Salary(String name, String address, int number, double salary) { super(name, address, number); // Call Employee constructor setSalary(salary); }
// Overridden mailCheck implementation @Override public void mailCheck() { System.out.println("Within Salary's mailCheck method:"); System.out.println("Mailing check to " + getName() + " (Salary: " + salary + ") at " + getAddress()); }
public double getSalary() { return salary; }
public void setSalary(double newSalary) { if (newSalary >= 0.0) { salary = newSalary; } else { System.err.println("Salary cannot be negative."); } }
public double computePay() { System.out.println("Computing salary pay for " + getName()); return salary / 52; // Weekly pay }}Demonstrating Polymorphic Behavior:
/* File: VirtualDemo.java */public class VirtualDemo { public static void main(String[] args) { // Reference and object are both Salary Salary salRef = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 52000.00);
// Reference is Employee, but object is Salary (Polymorphism) Employee empRef = new Salary("John Adams", "Boston, MA", 2, 60000.00); // Alternative using the previous Salary object: // Employee empRef = salRef;
System.out.println("Calling mailCheck using Salary reference:"); salRef.mailCheck(); // Calls Salary's mailCheck()
System.out.println("\nCalling mailCheck using Employee reference:"); empRef.mailCheck(); // Calls Salary's mailCheck() due to runtime object type!
// Compiler error if uncommented: computePay() is not in Employee class // empRef.computePay();
// To call Salary-specific methods via empRef, explicit casting is needed: if (empRef instanceof Salary) { Salary specificSalRef = (Salary) empRef; double weeklyPay = specificSalRef.computePay(); System.out.println("\nJohn Adams weekly pay: " + weeklyPay); } }}Output:
Constructing an EmployeeConstructing an EmployeeCalling mailCheck using Salary reference:Within Salary's mailCheck method:Mailing check to Mohd Mohtashim (Salary: 52000.0) at Ambehta, UP
Calling mailCheck using Employee reference:Within Salary's mailCheck method:Mailing check to John Adams (Salary: 60000.0) at Boston, MA
Computing salary pay for John Adams
John Adams weekly pay: 1153.846...Explanation:
- When
salRef.mailCheck()is called, both the compile-time type (Salary) and runtime type (Salary) havemailCheck(). TheSalaryversion is executed. - When
empRef.mailCheck()is called: -
- At compile time, the compiler checks the
Employeeclass formailCheck(). It finds it, so the code compiles.
- At compile time, the compiler checks the
-
- At run time, the JVM sees that
empRefactually points to aSalaryobject. Therefore, it executes the overriddenmailCheck()method from theSalaryclass.
- At run time, the JVM sees that
- This runtime selection of the appropriate overridden method is the essence of virtual method invocation and polymorphism in action.
- Calling methods specific to the subclass (like
computePay) requires checking the type withinstanceofand casting the reference variable.
Polymorphism enables writing flexible and extensible code. You can write methods that operate on superclass or interface types, and they will correctly work with any current or future subclass objects passed to them, invoking the appropriate specialized behavior.