Доступ к подклассам из Arraylist

Я пытаюсь создать простую ролевую игру на Java. Прямо сейчас я пытаюсь отобразить случайный выбор из 3 противников, чтобы игрок мог выбрать из списка JavaFX (battleOptions в коде). В моем основном классе у меня есть это нажатие кнопки, чтобы инициировать просмотр списка для заполнения противников.

public void battle() {
    if (!prep) {
        ArrayList<Enemy> classes = new ArrayList<>();
        Swordsman p1 = new Swordsman();
        classes.add(p1);

        Archer p2 = new Archer();
        classes.add(p2);

        for (int i = 0; i < 3; i++) {
            battleOptions.getItems().add((classes.get(randomnumber(0, 1))));
        }
    }
}

И это класс Enemy, упомянутый выше ArrayList:

public class Enemy {
    public static String name = "Enemy";
    
    int damage = 1;
    int hp = 1;
    int defense = 1;
    int mana = 1;
    int speed = 1;
    int luck = 1;
}
class Swordsman extends Enemy {
    public static String name = "Swordsman";
    public static double damage = 0.3;
    public static double hp = 0.15;
    public static double defense = 0.2;
    public static double mana = 0.1;
    public static double speed = 0.1;
    public static double luck = 0.15;
}

(Archer то же самое, что и Swordsman, за исключением названия и разницы в процентах)

В своем текущем состоянии этот код добавляет подклассы в представление списка как: com.example.test.Swordsman/Archer@x.

x отличается, и я предполагаю, что это какое-то место в памяти или что-то в этом роде. Очевидно, это не то, что я хочу показать игроку, скорее я хочу использовать Swordsman/Archer.name и показать это, но он показывает только значения вражеского класса для имени и всего остального, несмотря на то, что он показывает подкласс перед .

Я попытался изменить ArrayList на Object, но тогда это не позволило бы мне использовать какие-либо значения (я имею в виду .name и прочее, я не знаю, как они называются). И еще что-то, чего я не помню. Я хотел бы избежать жесткого кода для if (randomnumber==1) {...}, особенно на тот момент, когда я полностью конкретизирую и добавляю больше классов.

Извините за длинный пост, буду признателен за любую помощь/совет!

Я не понимаю логики наличия Swordsman расширений Enemy и использования int damage в Enemy и double damage в Swordsman. Это в значительной степени относится ко всем вашим переменным.

SedJ601 20.10.2022 06:58

Убедитесь, что вы понимаете, как и когда использовать static. Это может быть одной из причин, по которой у вас возникла эта проблема. Вам тоже нужно учиться Inheritance. Это также может быть причиной вашей проблемы.

SedJ601 20.10.2022 07:01

@ SedJ601 У меня была еще одна идея для основного класса Enemy: он в конечном итоге стал настоящим врагом с настоящей целочисленной статистикой, а не двойниками в подклассах, которые я использую в качестве общего процентного распределения очков статистики для умножения на уровень и статистику. -получение очков за уровень, но да, в настоящее время это бесполезно, так как я изменил его на использование экземпляра для подклассов.

Real Darkflare 20.10.2022 07:06

Не пытайтесь делать что-либо еще, пока не поймете, как и когда использовать static и как использовать inheritance. То, что вы делаете в настоящее время, показывает, что вы плохо понимаете ни одну из этих идей.

SedJ601 20.10.2022 07:25

Я согласен с тем, что заявил SedJ601. Прямо сейчас у вас есть инстанцируемый класс, в котором нет ничего, кроме статических полей. Это означает, что ни один экземпляр Swordman не будет отличаться. Например, когда один Swordman получает удар, все экземпляры Swordman теряют одинаковое количество здоровья, потому что статические поля являются общими для ВСЕХ экземпляров класса.

hfontanez 20.10.2022 07:29

Если бы вы могли, оставьте комментарий под моим ответом, чтобы увидеть, насколько он был полезен. Если это решило проблему, нажмите на галочку рядом с ней. Если это не так, напишите в комментариях, чего не хватает, чтобы я мог это исправить.

hfontanez 20.10.2022 16:06
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
6
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не решение. Вместо этого предложения по улучшению вашего кода, которые могут или не могут решить ваши проблемы.

Начнем с «Врага». Вполне нормально иметь базовый «вражеский» класс, который будет устанавливать значения по умолчанию для различных типов «вражеских» персонажей. Это обычное дело в играх. Персонажи обычно имеют параметры по умолчанию, а затем определенные расы или подтипы будут добавлять или вычитать очки в зависимости от атрибутов этого типа. Итак, давайте сделаем это для «Лучника» и «Мечника».

public class Enemy {
    
    protected final String id;

    protected int damage = 10;
    protected int hp = 10;
    protected int defense = 10;
    protected int mana = 10;
    protected int speed = 10;
    protected int luck = 10;
    
    protected Enemy () {
        Random rnd = new Random();
        int number = rnd.nextInt(99999999);
        id = String.format("%08d", number);
    }

    // omitted getters and setters
}

Я добавил последнее (постоянное) строковое поле id, чтобы получить уникальный идентификатор для каждого экземпляра. Я буду использовать это в методе подклассов toString() для отображения имени с уникальными идентификаторами. Таким образом, при отображении символов каждый из них будет отображаться по-разному, чтобы вы знали, что они не являются простыми копиями друг друга.

Вы можете переопределить все переменные в подклассе, как вы пытались сделать. Например, вы можете определить damage в обоих классах, а затем в методе получения вы можете добавить их вместе и вернуть значение следующим образом:

public int getDamage () {
    return this.damage + super.damage;
}

Но зачем это делать? Сделав поля в суперклассе защищенными, ваши подклассы будут иметь к ним прямой доступ. Если вам нужно добавить бонусные баллы в эти поля, просто добавьте бонусные/штрафные баллы в конструкторе.

public final class Swordsman extends Enemy {

    public static final String NAME = "Swordsman";

    public Swordsman() {
        damage += 20; // added 20 points to the default "damage" field value of 10
        // other fields here
    }
    // getters and setters defined in Enemy class

    @Override
    public String toString() {
        return "Swordsman ["+ id + "]";
    }
    
    @Override
    public String getStats() {
        return NAME + super.getStats();
    }
}

Это гораздо более чистый подход, потому что подклассы Enemy не должны переопределять то, что Enemy уже имеет. Единственное, что им нужно определить, это атрибуты, которые уникальны для их типа. Например, возможно, вам нужно определить бонус ближнего боя для мечников в ближнем бою, тогда как лучники могут не иметь бонусов ближнего боя. Точно так же у лучников может быть бонус к дальним атакам, а у мечников - нет. Эти атрибуты (поля) и методы должны быть добавлены к этим классам с уважением.

При запуске этого кода с помощью приведенного ниже фрагмента

public class Game {
    public static void main(String[] args) {
        Swordsman s1 = new Swordsman();
        Swordsman s2 = new Swordsman();
        Swordsman s3 = new Swordsman();
        Swordsman s4 = new Swordsman();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
    }
}

Он будет отображать что-то вроде этого:

Swordsman [04923128]
Swordsman [59484796]
Swordsman [11003419]
Swordsman [12543814]

Другие вещи, которые следует отметить в моей реализации:

  1. Нет полей static: если вам нужно, чтобы какой-то атрибут был общим для всех экземпляров класса, вам нужно будет использовать для этого static. Например, атрибут name является статическим в подклассах. Все экземпляры класса Swordsman будут помечены как «фехтовальщик». Я также сделал это final, чтобы значение атрибута нельзя было изменить. По сути, это поле является общей константой.
  2. Поля в классе Enemy защищены, чтобы они были доступны его подклассам.
  3. Класс фехтовальщика final, поэтому его нельзя расширить. Если вам нужно создать подкласс Swordsman, вам нужно будет удалить этот квалификатор.
  4. Подклассу не нужны геттеры и сеттеры. Они должны быть в классе Enemy

Другие вещи, которые вам нужно учитывать, каждый класс должен переопределять equals(), hashCode() и toString(). Ваша IDE должна быть в состоянии сгенерировать их для вас. Если класс не добавляет новые поля, подклассу не нужно будет повторно переопределять эти методы, за исключением toString(), потому что, скорее всего, вы захотите отобразить значение поля имени объекта (наряду с некоторыми другими вещами, такими как его хэш-код ).

И последнее, на что я хочу обратить внимание, если вы не знали, это мое предположение, основанное на опубликованном вами коде. Когда вы используете static для квалификации поля или метода, вы не переопределяете поле или метод. По сути, вы создаете новый метод или поле в подклассе с тем же именем и сигнатурой. Помните, статические члены принадлежат классу, а не экземпляру. Это еще одна причина, по которой вы не должны использовать static в своей реализации. Для того, что вы пытаетесь сделать, это просто не имеет никакого смысла.

Последнее, последнее, LOL

Почему бы не использовать для этого записи Java? Просто потому, что записи неизменяемы и эти символы нужно постоянно обновлять (урон во время боя, другие атрибуты при прокачке и т.д.) По этой причине лучше использовать обычный POJO, а не Java-запись. Я подумал, что важно указать на это для тех, кто задается вопросом.

Другие вопросы по теме