Polymorphism in Java
Of the core pillars of Object Oriented Programming, Polymorphism affords a great deal of flexibility. While inheritance allows for encapsulated and abstracted data to be shared, polymorphism allows for the redefining of both the data and the attached methods based on context.
Java supports polymorphic behavior through two means: Method Overloading and Method Overriding.
Demonstration
Say that I have an ArchiveItem
class that stores the basic details of a name and id. Here I'll demonstrate both overloading and overriding:
package polymorphism;
public class ArchiveItem {
private String id;
private String name;
// Overloaded constructor with all instance variables supplied
public ArchiveItem(String id, String name) {
this.id = id;
this.name = name;
}
// alternative constructor with only the name provided
public ArchiveItem(String name) {
this.name = name;
this.id = IDPackage.generate();
}
// Override the base equals() method
@Override
public boolean equals(Object compared) {
if (compared == this) return true;
if (!(compared instanceof ArchiveItem)) return false;
ArchiveItem comparedItem = (ArchiveItem) compared;
return this.id.equals(comparedItem.id);
}
// Override the base toString() method
@Override
public String toString() {
return String.format("%s: %s", this.id, this.name);
}
}
Taking a look at my constructors, I'm overloading the ArchiveItem
method by declaring the same method, but with a different number of arguments both times. When instantiated, Java will know which of the two methods to run based on what arguments are provided. Another way of putting it: One of the two methods will be called depending on their signature.
Looking further down, I've written an equals
and a toString
method. All Objects in Java come with these methods. Every class inherits from the base Java Object class, and on that class are implementations for equals
and toString
. In fact, toString
is what's called anytime you print an object to the console.
Without any adjustments, passing an object to System.out.println()
would return something like this:
ArchiveItem guitar = new ArchiveItem("Guitar");
System.out.print(guitar);
// "polymorphism.ArchiveItem@28d93b30"
The base print method will print the classname to the left of the @
symbol and the location in memory to the right. Typically, we want something more descriptive representing our class instance.
In the example above, I'm pringint instead the provided id and name of the ArchiveItem
By adding the @Override
annotation, I'm declaring that I'm intending to implement my own logic for the already inherited toString
method. The @Override
annotation is actually not necessary, but recommended. This will flag to the compiler to check that you're in fact overriding an existing method. Great for catching typos!
Putting It All Together
ArchiveItem piano = new ArchiveItem("Piano");
System.out.print(piano);
// "Piano, 93nkf903f"
Here it is in action! The ArchiveItem
is instantiated with only one argument, so it calls the matching method. One line down, I'm calling my implementation of toString
by passing my piano object into the print method.
Here is the same class but with a different constructor signature:
ArchiveItem piano = new ArchiveItem("custom-id", "Piano");
System.out.print(piano);
// "Piano, custom-id"