Если бы у animal был метод makeNoise(), который был бы переопределен в классе Dog and Cat, то удобно было бы сделать так:
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeNoise(); //bark-bark
cat.makeNoise(); //meow-meow
т.е. нам не важно, какой объект придет, dog или cat, мы единообразно можем заставить его мявкнуть или гавкнуть.
Это отделение абстракции от реализации. Понятный пример с музыкальными инструментами и методом playMusic. Или сервисом рассылки и его реализациями почтовая рассылка/sms-рассылка. Если представить себе, программу, которая рассылала почту, а теперь поступили новые требования, которые говорят, что нужно рассылать sms. В случае чистой архитектуры потребуется заменить всего одну строку:
Sender sender = new EmailSender();
на
Sender sender = new SmsSender();
и все будет работать, если оба класса реализуют метод send();
Но это уже больше про интерфейсы, т.е. полиморфизм, а не про наследование.
Но почему тогда когда мы пишем animal . bark() этот метод не вызывается, хотя находится в классе Dog
А когда мы пишем dog . bark() этот метод вызывается
Но ведь отличия только в типах переменных я этого не понимаю