What polymorphism is
Polymorphism, Greek for "many forms", means that one piece of code does different things depending on which subclass the object really is. You write a call such as animal.speak() once, and the right override runs for each object.
It builds on the last two lessons. Once Dog and Cat both extend Animal and override speak(), code that holds an Animal no longer needs to care which kind it is.
Why it matters
Suppose you write a method that makes every animal in a zoo speak:
void roll(Animal[] animals) {
for (Animal a : animals) {
a.speak();
}
}
This method works for every animal type that exists today and for every one added later. If you create a Cow class tomorrow, with its own speak(), the loop runs it with no edits to roll. The code keeps working as the class hierarchy grows, and that is the main benefit.
Static type and dynamic type
Two types are involved in every call:
- The static type is the type the variable was declared with. For
Animal ait isAnimal. The compiler uses it to check thatspeak()exists. - The dynamic type is the class of the object the variable actually refers to at runtime, such as
DogorCat. Java uses it to choose which version ofspeak()runs.
Choosing the method at runtime, from the object's real class, is called dynamic dispatch. In Java it applies to all instance methods unless they are final or private, so a plain override is enough and no keyword on the parent is needed.
Parent-typed variables
A subclass object can be stored in a variable of its parent type without any cast, because it is a kind of the parent:
Animal pet = new Dog();
pet.speak();
Here pet has the static type Animal and refers to a Dog, so the call runs Dog's speak(). The reverse direction is stricter: through pet you can call only what Animal declares. A method that exists only in Dog, such as fetch(), is not visible there, and the compiler refuses the call.
The lesson's exercise, step by step
The starter code already defines Animal, whose speak() prints ..., and two subclasses, Dog and Cat. Each overrides speak(). The body of mainFunction is empty. You write two steps:
- Collect the animals. Declare an
Animal[]namedanimalswith an array initializer that holdsnew Dog()andnew Cat(). Storing both under the parent type means the code that uses the array knows only that it holds animals. - Loop and call. Use a for-each loop with an
Animalvariableaand calla.speak()inside it. The same call runsDog's version first andCat's version second.
The full program:
class Animal {
public void speak() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof");
}
}
class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow");
}
}
class Solution {
public void mainFunction() {
Animal[] animals = { new Dog(), new Cat() };
for (Animal a : animals) {
a.speak();
}
}
}
It prints:
Woof
Meow
The array's element type is Animal and the loop variable is Animal, yet the output shows the subclass behavior. Nothing in the loop mentions Dog or Cat. If you removed the overrides, both lines would print ..., the parent's version.
Details worth knowing
- The initializer in braces.
Animal[] animals = { new Dog(), new Cat() };is shorthand for creating an array of two elements and filling it. It is allowed only in a declaration. Elsewhere you writenew Animal[] { ... }. - The array holds references. It doesn't contain whole objects. Each slot refers to an object, and the slots have the type
Animal, so a slot can refer to any subclass object. - Checking the real class. The expression
a instanceof Catistruewhen the object behindais aCat. Needing it often is a sign the design should use an overridden method instead. - The same idea with interfaces. A variable can also have an interface type, and calls through it dispatch to the implementing class in the same way.
- Fields don't behave this way. Dynamic dispatch applies to instance methods only. Reading a field uses the variable's static type.
Common mistakes
- Declaring the array as
Dog[]. ACatcan't be stored in it. The element type has to be the common parent,Animal. - Calling a subclass-only method through the parent type.
a.fetch()fails to compile iffetchis declared only inDog. - Forgetting to override. If
Catmisspelledspeak, the loop would print the parent's...for the cat.@Overrideturns that mistake into a compile error. - Using a
staticmethod. Static methods are not dispatched by the object's class, so a polymorphic method must be an instance method. - Calling
speak()on the array.animals.speak()is an error. The call belongs on each element.
Key takeaways
- Polymorphism lets one call run different code depending on the object's actual class.
- A variable of a parent type can refer to any subclass object.
- The compiler checks the static type, and the runtime chooses the method from the dynamic type.
- In Java, every instance method that is not
finalorprivateworks this way, with no extra keyword. - Code written against the parent type keeps working when new subclasses are added.