В принципе, нет, вы не можете.Концептуальная причина в том, как родовые типы связаны.Когда вы создаете класс, такой как Zoo<T extends Animal>
, T
не означает , что означает «любой тип, который расширяет Animal
», это означает, что «определенный тип, который расширяет Animal
, будет предоставлен вво время выполнения».Обычно это позволяет вам делать то, что вы хотите, но ваш случай, по-видимому, проверяет границы (ba-dum-ткань) этой системы.
Я думаю, что более грубый ответ должен идти в подстановочный знак(?
) система привязки - что-то в ? extends A & B
означает, что она не может доказать, что тип C
, который расширяет A & B & D
, действительно соответствует.
(хуже) дизайн, который выполняет вашу цель выглядитнапример:
public static abstract class Zoo{
private List<Animal> animals = new ArrayList<>();
protected void addAnimalHelper(Animal animal) {
this.animals.add(animal);
}
}
public static class BirdZoo extends Zoo {
public <T extends Animal & Bird> void addAnimal(T animal) {
addAnimalHelper(animal);
}
}
BirdZoo birdZoo = new BirdZoo();
birdZoo.addAnimal(new Ostrich()); // ok
birdZoo.addAnimal(new Sparrow()); // ok
birdZoo.addAnimal(new Meerkat()); // Meekrat doesn't conform to Bird
birdZoo.addAnimal(new Buffalo()); // Buffalo doesn't conform to Bird
С параметром типа, закодированным в сигнатуре метода, система типов может свободно выбирать новый T
при каждом вызове метода, что позволяет ему привязываться к Страусу за один вызов иВоробей на следующем.
Очевидно, у этого есть некоторые недостатки по сравнению с желаемым дизайном:
- Базовое хранилище является «сырым» (в данном случае просто
Animal
), оно работаеттипизированному подклассу для принудительного набора элементов - Аналогично, извлечение элементов из хранилища и сохранение известного типа является сложным / грязным.
- Требуется стандартный метод в каждом подклассе + доступ к немуlper для кодирования типа в методах.
Еще одна опция, которая работает только частично:
// Note - inheritance isn't used here since it breaks due to method overriding issues.
public static class BirdZoo<T extends Animal & Bird> {
private List<T> animals = new ArrayList<>();
public <X extends Animal & Bird> void addAnimal(X animal) {
this.animals.add((T)animal); // Unchecked cast warning
}
public List<T> getAnimals() {
return Collections.unmodifiableList(animals);
}
}
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
wildBirdZoo.addAnimal(new Meerkat()); // error - incompatible types
wildBirdZoo.addAnimal(new Buffalo()); // error - incompatible types
Тот факт, что он жалуется на приведение X
к T
указывает на проблему, вокруг которой мы танцуем.Например, когда экземпляр создается с BirdZoo<?>
, мы в основном в порядке.С другой стороны, представьте, что зоопарк был построен с использованием BirdZoo<Ostrich>
и ostrichBirdZoo.addAnimal(new Sparrow());
.Тогда у нас есть T=Ostrich
и X=Sparrow
.И T
, и X
расширяют и Animal
, и Bird
, но T != X
, НО , статическая проверка и проверка типов не достаточно умны, чтобы сказать!
BirdZoo<?> wildBirdZoo = new BirdZoo<>();
wildBirdZoo.addAnimal(new Ostrich()); // ok
wildBirdZoo.addAnimal(new Sparrow()); // ok
BirdZoo<Ostrich> ostrichBirdZoo = new BirdZoo<>();
ostrichBirdZoo.addAnimal(new Ostrich()); // ok
ostrichBirdZoo.addAnimal(new Sparrow()); // ok and doesn't throw at runtime --> Sparrow to Ostrich cast succeeds.
System.out.println(wildBirdZoo.getAnimals());
System.out.println(ostrichBirdZoo.getAnimals()); // Contains a sparrow...?
Что, похоже, полностью нарушает систему типов.
Короче говоря ... это может работать не так, как вы хотите.