Не могу понять полиморфизм Java

Учил джаву и никак не могу понять, зачем использовать полиморфизм, для чего делать, пустой класс скажем человека, объявлять пустые методы, а после этого создавать одного, второго, третьего и т.д. человека, используя класс человека? Не проще ли сразу создать классы людей? Ведь чтобы их использовать в любом случае придётся к ним обратится, так в чём суть?

Смотрел разные примеры, видео, но этот вопрос меня никак не покидает, вот к примеру последнее видео, которое я смотрел — youtu.be/...​uaF0s?si=MtKNlgzhXWW-MQCX

Здесь автор делает пример из доты, в котором делает с помощью полиморфизма из статической типизации — динамическую, ну как я понял.

А чем это поможет если по сути, класс Hero, имеет в себе классы всех персонажей, то есть по логике, когда с помощью него объявляется 5 переменных с героями, в каждой переменной нет конкретного героя, как понять кого надо вызвать именно? Как я понимаю уже в будущем коде надо это реализовать, но тогда каким образом понять какого героя надо выбрать? И в последующем, вызвать класс нужного героя, не лучше ли сразу определиться и вызвать нужные классы, вместо того чтобы вызывать все, а работать с определёнными? Или может я что-то не понимаю, в любом случае если у вас есть пример полиморфизма, в котором не возникнет вопрос, а нафига всё усложнять, то буду рад его прочитать, потому как что такое полиморфизм, и как он примерно работает я знаю, но не понимаю.


Ответы (2 шт):

Автор решения: e1st0rm

Максим! Полиморфизм - множество форм. Первое, что пришло в голову, это пример с животными. Создадим интерфейс животное (Animal).

interface Animal {
    String voice();
}

В нём только один метод, voice(), без определения поумолчанию. Так как животные могут быть разные, звуки они тоже издают разные и на данном уровне абстракции мы не знаем, конкретно какие животные будут реализовывать этот интерфейс. Потому нам не известно, какие звуки они будут издавать. Добавим классы каких-то животных, в моём случае их три: Cat, Dog, Mouse.

class Cat implements Animal {

    @Override
    public String voice() {
        return "Мяу, мяу..."; // Кошка мяукает
    }
}

class Dog implements Animal {

    @Override
    public String voice() {
        return "Гав, гав..."; // Собака лает
    }
}

class Mouse implements Animal {

    @Override
    public String voice() {
        return "Пи, пи..."; // Мышь пищит
    }
}

Теперь создадим класс Main с методом main(), откуда и будет стартовать наша программа.

public class Main {
    public static void main(String[] args) {
        Animal[] animals = {
            new Dog(),
            new Cat(),
            new Mouse()
        };

        for (Animal animal : animals) {
            System.out.println(animal.getClass().getSimpleName() + ": " + animal.voice());
        }
    }
}

В методе main() создаём массив типа Animal и добавляем в него наших животных, после чего в цикле проходимся по массиву и вы вводим их голоса в консоль. Обрати внимание, что массив у нас имеет тип Animal, т.е. тип интерфейса. Интерфейс как бы обязует каждый класс, который будет его реализовывать, создать свой метод voice(), который и будет возвращать нужный звук, в зависимости от того, какое именно животное его издаёт. Это и есть полиморфизм, точнее одно из его определений. Вообще в сети много ресурсов, где об этом можно почитать, более подробно. Если, что-то не понятно, уточняй, что именно.

→ Ссылка
Автор решения: Котлетка еее

Хм, давайте на примере, который вы как раз и привели. Герои из Доты. Есть, скажем, абстрактный класс Hero, который какую-то общую для всех героев логику может содержать, повышение уровня в зависимости от количества опыта, смерть при 0 хп, ну и так далее.

Однако большая часть его методов, как вы сказали - пустые. То есть методы объявлены, но не реализованы, у них нет тела. Для темы с героем они могут быть, к примеру, такие:

public abstract int calcHandDamage();
public abstract void dance();
public abstract void castFirstSkill();
public abstract void castSecondSkill();
public abstract void castThirdSkill();
public abstract void castFourthSkill();

И так далее. Вот у нас есть абстрактный герой Hero. Знать, как отыгрывать, будут его наследники. Они реализуют заявленные абстрактными методы, каждый по-своему. Но вот в чём фишка. С самыми разными героями, хоть убийцами, хоть магами, хоть стрелками, с разными пассивками и активными скиллами, можно будет взаимодействовать одним и тем же образом – через эти методы.

Вы спросили, почему бы сразу не создать классы этих героев, зачем нам какой-то пустой Hero? Дело вот в чём. Заранее мы можем не знать, какой из героев нам понадобится. Разве разработчики Доты знают, какого персонажа каждый игрок решит пикнуть в очередной катке? Поэтому, в зависимости от пика игрока, создаётся объект нужного героя. Но чтобы в ходе игры вызывать эти все методы, нам вовсе необязательно знать, какого именно персонажа выбрал игрок. Ведь у них всех единые методы. Единая суть во многообразии форм.

Таким образом, если игрок выбрал Пуджа:

Hero selectedHero = new Pudge(); // класс Pudge наследуется от Hero

Если игрок выбрал Инвокера:

Hero selectedHero = new Invoker(); // класс Invoker тоже наследуется от Hero

А уж если решил выбрать Анти Мага:

Hero selectedHero = new AntiMage(); // не поверите, но и класс AntiMage - наследник класса Hero

Затем вам просто нужно вызывать соответствующие методы объекта, хранящегося в selectedHero. Вызван будет метод именно того объекта, который вы поместили в переменную selectedHero. Хотя она имеет тип Hero, но хранится-то там объект другого класса - класса-наследника Hero. Так что если там Пудж, то будут вызваны методы, реализованные для Пуджа. И так далее.

selectedHero.dance();
selectedHero.castFirstSkill();
int damage = selectedHero.calcHandDamage();

Вы вообще не обязаны знать, какой именно герой там хранится. Ко всем ним одинаково можно обращаться. Не нужно прописывать кучу if-ов или ещё какой мути. Структура программы и её логика становятся чище, прозрачнее, красивее. Примерно в этом заключается суть полиморфизма.

→ Ссылка