Не понимаю почему не передаються аргументы в List

Рейтинг: 0Ответов: 3Опубликовано: 11.01.2023

Должен написать класс House, который имеет поле residents типа List, и метод enter(Object resident). Также есть 4 класса: Dog, Puppy (extends Dog), Cat, Kitty (extends Cat). И суть в том, что метод enter() должен добавлять в класс House животных, но так чтобы если первый элемент при добавлении была кошка, могли добавляться только кошки, соответствующая ситуация с собаками.

Вот так выглядит метод Main.

public class Main {

    public static void main(String[] args) {
        Dog rex = new Dog("Rax");
        Puppy randy = new Puppy("Randy");
        Cat barbos = new Cat("Barbos");
        Kitten murzik = new Kitten("Murzik");

        House dogHouse = new House();
        dogHouse.enter(rex);
        dogHouse.enter(randy);
        dogHouse.enter(murzik); //This must fail on compilation stage if you parameterize the dogHouse. Delete the line when solution is ready
        System.out.println(dogHouse);

        House catHouse = new House();
        catHouse.enter(barbos);
        catHouse.enter(murzik);
        catHouse.enter(rex); //This must fail on compilation stage if you parameterize the catHouse. Delete the line when solution is ready
        System.out.println(catHouse);
    }
}

Только начинаю учить дженерики и пробовал такой способ но выдает ошибку, незнаю что делать.

public class House <T> {

    private final List <? extends T> residents = new ArrayList<>();

    public <T> void enter(T resident) {
        residents.add(resident);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("There are following residents in the house:\n");
        for (Object resident : residents) {
            builder.append(resident.toString()).append("\n");
        }
        return builder.toString();
    }
}

Ответы

▲ 2Принят

Во-первых, в методе main класса Main вы, создавая объект просто от House, используете так называемую "сырую" форму. При таком подходе на место обобщённого типа подставляется класс Object, и поэтому вызывать метод enter объекта класса House вы сможете, передавая в него объект любого типа.

Поэтому при создании объекта нужно сразу явно указать, какой класс вы планируете использовать. Класс собак Dog или класс котов Cat. То есть, например:

House<Dog> dogHouse = new House<>();
dogHouse.enter(rex);

Во-вторых, в классе House список следует объявить так:

private final List <T> residents = new ArrayList<>();

В-третьих, в методе enter должен быть использован тот обобщённый тип T, который вы указываете классу House, а не собственный явно прописанный тип T, то есть:

public void enter(T resident) {
    residents.add(resident);
}

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

▲ 2

Нужно указать, что класс House является дженериком для некоего типа, унаследованного от класса общего для Cat и Dog (но не Object, так как толку тогда будет мало).

Например, пусть такой класс называется Pet:

@Data
@RequiredArgsConstructor
class Pet {
    private final String name;
}

И для него реализована соответствующая иерархия:

Pet
 ^----- Dog <---- Puppy
 |
 ^----- Cat <---- Kitten

Тогда класс House будет типизирован по какому-то конкретному типу-наследнику Pet:

@Data
class House<T extends Pet> {
    private List<T> residents = new ArrayList<>();

    public void enter(T pet) {
        residents.add(pet);
    }
}

Соответственно, его экземпляры должны быть созданы типизированными, а не "сырыми":

House<Dog> dogHouse = new House<>();
// ...
House<Cat> catHouse = new House<>();

и будет получена "нужная" ошибка на этапе компиляции.

введите сюда описание изображения

При необходимости поселить кошек вместе с собаками, можно будет создать дом, принимающий любых питомцев:

House<Pet> petHouse = new House<>();
petHouse.enter(rex);
petHouse.enter(randy);
petHouse.enter(murzik); // OK!

Если же нужно динамически инициализировать условно "сырую" коллекцию, запоминать тип элемента при первом добавлении и проверять его при всех последующих, это можно реализовать не рекомендуемым следующим образом:

@Data
class RawHouse {
    private List<Object> residents = null;
    private Class clazz = null;

    public void enter(Object pet) {
        if (clazz == null) {
            clazz = pet.getClass();
            residents = new ArrayList<>();
        }
        if (!clazz.isAssignableFrom(pet.getClass())) {
            throw new RuntimeException("Bad pet class: " + pet.getClass());
        }
        residents.add(pet);
    }
}
RawHouse dogHouse = new RawHouse();
dogHouse.enter(rex);
dogHouse.enter(randy);
dogHouse.enter(murzik); // Нет ошибки компиляции, есть ошибка выполнения
System.out.println(dogHouse);
Exception in thread "main" java.lang.RuntimeException: Bad pet class: class Kitten
    at RawHouse.enter(SOPetHouses.java:85)
    at SOPetHouses.main(SOPetHouses.java:18)

Разумеется, никакая верификация типа и ошибка на этапе компиляции здесь невозможна.

▲ 1

Вам не нужны дженерики чтобы решить задачу. List хранит объекты Object - фактически лишён типа. При вселении проверяется что новый житель имеет тот же тип что и самый первый житель домика. Наследники также пропускаются. Совместимость проверяет выражением residents.get(0).getClass().isInstance(resident). При несовместимости бросаем исключение:

class House {
    private final List<Object> residents = new ArrayList<>();

    public void enter(Object resident) {
        if (
            !residents.isEmpty() &&
            !residents.get(0).getClass().isInstance(resident)
        ) {
            throw new RuntimeException("AAA!");
        }
        residents.add(resident);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("There are following residents in the house:\n");
        for (Object resident : residents) {
            builder.append(resident.toString()).append("\n");
        }
        return builder.toString();
    }
}