Подтвердить что ты не робот

Generic: зачем использовать "класс A <E extends Superclass>" вместо "class B <Superclass>"?

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

Мой вопрос может показаться странным. Зачем использовать E extends Superclass вместо Superclass? Пожалуйста, ознакомьтесь с приведенным ниже кодом.

class Hunter <E extends Animal>{}

class Keaper <Animal>{}
    /*edit: as AR.3 and other pointed out, Keaper<Animal> is the same as Keaper<E>.
 Animal  is just a type parameter here*/

class Animal{}

class Cat extends Animal{}

class Dog extends Animal{}


public class TestGeneric {
    public static void main(String[] args) {
        Hunter<Cat> hunter1 = new Hunter<>();
        Hunter<Dog> hunter2 = new Hunter<>();
        Hunter<Animal> hunter3 = new Hunter<>();

        ArrayList<Hunter> hunters=  new ArrayList<>();
        hunters.add(hunter1);
        hunters.add(hunter2);
        hunters.add(hunter3);

        Keaper<Cat> keaper1 = new Keaper<>();
        Keaper<Dog> keaper2 = new Keaper<>();
        Keaper<Animal> keaper3 = new Keaper<>();
//Edit: as AR.3 and others pointed out, Keaper<String> is also legal here.

        ArrayList<Keaper> keapers=  new ArrayList<>();
        keapers.add(keaper1);
        keapers.add(keaper2);
        keapers.add(keaper3);
    }
}

Я чувствую, что E extends Animal почти такой же, как Animal, за исключением того, что первый может предоставить дополнительную информацию. Любая идея?

4b9b3361

Ответ 1

Фактически Animal, как заявлено в определении класса Keaper, не что иное, как параметр типа (было бы более условно просто использовать для него одну букву, например Keaper<T>). Таким образом, это совершенно другой диапазон возможных аргументов типа: класс Keaper может принимать любой тип как параметр, а не только Animal s. Вы можете написать Keaper<String>, и он будет компилироваться отлично, что явно не то, что вы хотите.

То, что вы намеревались сделать, невозможно с помощью generics: вы не можете заставить класс быть общим с одним возможным аргументом типа, связанным с ним. Вы можете ограничить диапазон связанных типов, например, любым типом, который расширяет определенный тип, который вы уже сделали с классом Hunter.

Также, как упоминалось в комментарии @JHH, вам следует избегать использования Keaper в качестве необработанного типа в списке, иначе код не будет полностью общим. Вместо ArrayList<Keaper> keapers = new ArrayList<>() вы можете написать ArrayList<Keaper<? extends Animal>> keapers = new ArrayList<>().

Ответ 2

Обычно вы должны использовать extends, если хотите сохранить определенный тип параметра. Рассмотрим этот пример:

public class TestGeneric {

    public static <T extends Animal> T changeExtendedAnimal(T extendedAnimal) {
        return extendedAnimal;
    }

    public static Animal changeAnimal(Animal animal) {
        return animal;
    }

    public static void main(final String[] args) {
        Cat changedCat1 = changeExtendedAnimal(new Cat()); //Compiles fine. 
        Cat changedCat2 = changeAnimal(new Cat()); //Won't compile: Type mismatch: cannot convert from Animal to Cat
    }
}

Здесь метод changeExtendedAnimal() использует extends, и поэтому компилятор знает, что вы получите Cat назад, если вы вызовете метод с помощью Cat.
С другой стороны, с помощью метода changeAnimal() компилятор не может этого знать, и вы получаете ошибку времени компиляции.