Polymorphism provides a way to separate low-level details from the main flow of the program, by restricting the details to subclasses, while the program only knows about the higher-level classes. Polymorphism lets the program work in terms of generalizations.
To extend your example a little we'd have
abstract class Animal {
public abstract void makeNoise();
}
class Cat extends Animal {
public void makeNoise() { System.out.println("meow");}
}
class Dog extends Animal {
public void makeNoise() { System.out.println("woof");}
}
class Demo {
public static void main(String[] args) {
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Cat());
animals.add(new Dog());
for (Animal animal: animals) {
animal.makeNoise();
}
}
}
Here the Demo program can run through the animals list without making any special cases for which animal is a dog and which one is a cat. That means I can add new subclasses of Animal and the for-loop contents never have to change.
If it wasn't for polymorphism the code would look like
class Dog {
public void bark() {System.out.println("woof");}
}
class Cat {
public void meow() {System.out.println("meow");}
}
class Demo {
public static void main(String[] args) {
ArrayList<Object> animals = new ArrayList<Object>();
animals.add(new Cat());
animals.add(new Dog());
for (Object animal: animals) {
if (animal instanceof Dog) {
((Dog)animal).bark();
} else if (animal instanceof Cat) {
((Cat)animal).meow();
}
}
}
}
and now there is no commonality about the animal classes that the program can make use of. Without any useful generalization to use Demo has to know a lot more about the details. Every new kind of animal introduced will necessitate an addition to the for-loop body.