Hi! Today we conclude a series of lessons on the principles of OOP. In this lesson, we'll talk about polymorphism. How to use polymorphism - 1Polymorphism is the ability to work with several types as if they were the same type. Moreover, the objects' behavior will be different depending on their type. Let's take a closer look at this statement. Let's start with the first part: 'the ability to work with several types as if they were the same type'. How can different types be the same? It sounds a little strange :/ In fact, it's all very simple. For example, this situation arises during ordinary use of inheritance. Let's see how that works. Suppose we have a simple Cat parent class with a single run() method:
public class Cat {

   public void run() {
       System.out.println("Run!");
   }
}
Now we'll create three classes that inherit Cat: Lion, Tiger and Cheetah.
public class Lion extends Cat {

   @Override
   public void run() {
       System.out.println("Lion runs at 80 km/h");
   }
}

public class Tiger extends Cat {

   @Override
   public void run() {
       System.out.println("Tiger runs at 60 km/h");
   }
}

public class Cheetah extends Cat {

   @Override
   public void run() {
       System.out.println("Cheetah runs at up to 120 km/h");
   }
}
So we have 3 classes. Let's model the situation where we can work with them as if they were the same class. Imagine that one of our cats is sick and needs help from Dr. Dolittle. Let's try creating a Dolittle class that can heal lions, tigers, and cheetahs.
public class Dolittle {

   public void healLion(Lion lion) {

       System.out.println("Lion is healthy!");
   }

   public void healTiger(Tiger tiger) {

       System.out.println("Tiger is healthy!");
   }

   public void healCheetah(Cheetah cheetah) {

       System.out.println("Cheetah is healthy!");
   }
}
It seems like the problem is solved: the class is written and ready to go. But what will we do if we want to extend our program? We currently only have 3 types: lions, tigers, and cheetahs. But there are more than 40 types of cats in the world. Imagine what would happen if we added separate classes for manuls, jaguars, Maine Coons, house cats, and all the rest. How to use polymorphism - 2The program itself will, of course, function, but we have to constantly add new methods to the Dolittle class to heal each type of cat. As a result, it will grow to unprecedented sizes. This is where polymorphism — "the ability to work with several types as if they were the same type" — comes in. We don't need to create countless methods to do the same thing — heal a cat. One method is enough for all of them:
public class Dolittle {

   public void healCat(Cat cat) {

       System.out.println("The patient is healthy!");
   }
}
The healCat() method can accept Lion, Tiger, and Cheetah objects — they're all instances of Cat:
public class Main {

   public static void main(String[] args) {

       Dolittle dolittle = new Dolittle();

       Lion simba = new Lion();
       Tiger shereKhan = new Tiger();
       Cheetah chester = new Cheetah();

       dolittle.healCat(simba);
       dolittle.healCat (shereKhan);
       dolittle.healCat(chester);
   }
}
Console output: The patient is healthy! The patient is healthy! The patient is healthy! So our Dolittle class works with different types as if they were the same type. Now let's tackle the second part: "moreover, the objects' behavior will be different depending on their type". It's all very simple. In nature, every cat runs in a different way. At a minimum, they run at different speeds. Among our three felines, the cheetah is the fastest, while the tiger and lion run slower. In other words, their behavior is different. Polymorphism does more than just let us use different types as one. It also lets us remember their differences, preserving the behavior specific to each of them. The following example illustrates this. Suppose that our cats, after a successful recovery, decided to enjoy a little run. We'll add this to our Dolittle class:
public class Dolittle {

   public void healCat(Cat cat) {

       System.out.println("The patient is healthy!");
       cat.run();
   }
}
Let's try running the same code to treat three animals:
public static void main(String[] args) {

   Dolittle dolittle = new Dolittle();

   Lion simba = new Lion();
   Tiger shereKhan = new Tiger();
   Cheetah chester = new Cheetah();

   dolittle.healCat(simba);
   dolittle.healCat(shereKhan);
   dolittle.healCat(chester);
}
And here's what the results look like: The patient is healthy! Lion runs at 80 km/h. The patient is healthy! Tiger runs at 60 km/h. The patient is healthy! Cheetah runs at up to 120 km/h Here we clearly see that objects' specific behavior is preserved, even though we passed all three animals to the method after 'generalizing' them to Cat. Due to polymorphism, Java remembers well that these aren't simply any three cats. They are a lion, a tiger, and a cheetah, which each run differently. This illustrates polymorphism's main advantage: flexibility. When we need to implement some functionality shared by many types, then lions, tigers, and cheetahs simply become 'cats'. All animals are different, but in some situations a cat is a cat, regardless of its species :) Here's some video confirmation for you.
When this 'generalization' is unwanted and we instead need each species to behave differently, each type does its own thing. Thanks to polymorphism, you can create a single interface (set of methods) for a wide range of classes. This makes programs less complicated. Even if we expand the program to support 40 types of cats, we would still have the simplest interface: a single run() method for all 40 cats.