Polymorphism means "many forms", and it occurs when we have many classes that are related to each other by inheritance.
Like we specified in the previous chapter; Inheritance lets us inherit attributes and methods from another class. Polymorphism uses those methods to perform different tasks. This allows us to perform a single action in different ways.
For example, think of a superclass called Animal that has a method called animalSound(). Subclasses of Animals could be Pigs, Cats, Dogs, Birds - And they also have their own implementation of an animal sound (the pig oinks, and the cat meows, etc.).
Polymorphism is useful for code reusability: reuse attributes and methods of an existing class when you create a new class. It also allows you to treat objects of different classes in a uniform way.
In this example, we have a base class Animal and two subclasses, Pig and Dog. They all have a method animalSound(), but it is implemented differently in each subclass. This is called method overriding.
class Animal {
public void animalSound() {
System.out.println("The animal makes a sound");
}
}
class Pig extends Animal {
// Method overriding
public void animalSound() {
System.out.println("The pig says: wee wee");
}
}
class Dog extends Animal {
// Method overriding
public void animalSound() {
System.out.println("The dog says: bow wow");
}
}
class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal(); // Create a Animal object
Animal myPig = new Pig(); // Create a Pig object
Animal myDog = new Dog(); // Create a Dog object
myAnimal.animalSound();
myPig.animalSound();
myDog.animalSound();
}
}
Notice that even though myPig and myDog are declared as type Animal, the Java Virtual Machine (JVM) knows their actual object type at runtime (Pig and Dog) and calls the correct overridden method. This is called runtime polymorphism or dynamic method dispatch.
Java actually supports two distinct types of polymorphism:
Animal example above!When working with polymorphism, you will frequently encounter the terms "upcasting" and "downcasting."
Upcasting is casting a child object to a parent reference. Java does this automatically and safely. It is what makes polymorphism possible.
Syntax:
// Upcasting: Dog is automatically cast to an Animal reference Animal myPet = new Dog();
Animal class blueprint. If Dog has a specific method called fetch(), you cannot call myPet.fetch() because the compiler only looks at the Animal reference.If you need to access those child-specific methods, you must downcast the parent reference back to the child type. You must do this manually using parentheses.
Syntax:
Animal myPet = new Dog();// Downcasting: Manually casting the Animal reference back to a Dog Dog myDog = (Dog) myPet; myDog.fetch(); // Now we can call Dog-specific methods!
Pig to a Dog), Java will throw a ClassCastException at runtime and crash your program.instanceof OperatorTo prevent ClassCastException crashes when downcasting, you should always check the actual object type first using the instanceof operator.
class Animal {}
class Dog extends Animal {
public void fetch() { System.out.println("Fetching a stick!"); }
}
public class Main {
public static void main(String[] args) {
Animal myPet = new Dog(); // Upcast
// Safe Downcasting
if (myPet instanceof Dog) {
Dog myDog = (Dog) myPet;
myDog.fetch();
} else {
System.out.println("This animal is not a dog.");
}
}
}
✨ Modern Java Feature (Java 16+): Pattern Matching for
instanceofallows you to declare the cast variable directly inside theifstatement, saving you a line of code!
if (myPet instanceof Dog myDog) { myDog.fetch(); }
@Override whenever you override a method; it lets the compiler catch spelling or signature mistakes.instanceof when a well-designed overridden method can express the behavior.What concept allows a subclass to provide a specific implementation of a method already provided by its parent class?