Путаница с коллекциями вложенных генериков
Пожалуйста, помогите мне понять, почему add1()
а также add4()
сообщить об ошибках и почему add2()
а также add3()
нет. В частности, приведите примеры нежелательных последствий, если компилятор разрешил компилировать каждый из них.
class InnerTypeConfusion {
interface Animal {}
class Dog implements Animal {}
class Room<T> {
void add(T t) {}
}
void add1(Room<? extends Animal> room) {
// Error: The method add(capture#1-of ? extends Animal) in the type
// Room<capture#1-of ? extends Animal> is not applicable for the
// arguments (Dog)
room.add(new Dog());
}
void add2(Room<Animal> room) {
room.add(new Dog());
}
class Cage<T> {}
void add3(Room<Cage<? extends Animal>> room) {
room.add(new Cage<Dog>());
}
void add4(Room<Cage<Animal>> room) {
// The method add(Cage<Animal>) in the type Room<Cage<Animal>> is not
// applicable for the arguments (Cage<Dog>)
room.add(new Cage<Dog>());
}
}
3 ответа
В методе void add1(Room<? extends Animal> room)
, вы определяете, что метод принимает Room
который держит Animal
, Например, это может быть Room<Cat>
, или же Room<Dog>
--четное Room<Animal>
для содержания всех видов животных. Однако имейте в виду, что комната была создана вне этого вызова метода, и вы не можете делать какие-либо предположения о типе комнаты, кроме того, что она содержит либо конкретного животного.
add1(new Room<Dog>()); // give the method a room for dogs
add1(new Room<Cat>()); // give the method a room for cats
add1(new Room<Animal>()); // give the method a room for any animal
Но как только вы попадаете в метод, вы не можете точно знать, какой тип комнаты был пройден.
Было бы правильно назвать метод с комнатой только для птиц add1(new Room<Bird>())
, как Bird
действительно распространяется Animal
, Однако в теле метода вы добавляете Dog
внутрь. Вот почему это недействительно, мы не можем поставить Dog
объекты в Room<Bird>
, Это Room
каких- то животных, а не Room
любого вида животных.
Если вы хотите написать метод, который добавляет собаку в комнату, подходящую для добавления собак (но не ограничиваясь только комнатами только для собак), вы должны написать ее с подписью addDogToRoom(Room<? super Dog> room)
за этот ответ. Этот метод может принять Room<Animal>
так же как Room<Dog>
и еще в рамках метода добавьте новых собак в комнату.
Как насчет add4
Это то же самое, но наоборот. С Room<Cage<Animal>>
вы указываете, что метод требует определенного типа комнаты - комната, которая позволяет только клетки, которые содержат любой вид Animal
, Но тогда вы пытаетесь поставить Cage<Dog>
в него, клетку, которая позволяет только собак. Следовательно, это снова недействительно.
Дополнение относительно комментария:
Допустим, есть клетки, предназначенные для содержания кошек Cage<Cat>
и клетки, предназначенные для содержания собак Cage<Dog>
, Существуют также универсальные клетки, в которых могут содержаться любые виды животных. Cage<Animal>
, Это три разных типа клеток, они не могут заменить друг друга, так как имеют совершенно разную архитектуру и дизайн.
void method(Cage<Dog>)
означает, что метод нуждается в одной клетке для собак.void method(Cage<Animal>)
означает, что метод нуждается в одной универсальной клетке.void method(Cage<? extends Animal>)
означает, что метод нуждается в любой клетке для животных. Клетка для собаки, кошка или универсальная клетка.
Комнаты - это еще один уровень абстракции - визуализируйте их как комнаты с клетками внутри. Там может быть комната для хранения клеток для кошек Room<Cage<Cat>>
, помещение для хранения собачьих клеток Room<Cage<Dog>>
, помещение для хранения универсальных клеток Room<Cage<Animal>>
и комната для хранения нескольких видов клеток для животных Room<Cage<? extends Animal>>
, Следовательно, применяются те же правила:
void method(Room<Cage<Dog>>)
- комната с собачьими клеткамиvoid method(Room<Cage<Cat>>)
- комната с клетками для кошекvoid method(Room<Cage<Animal>>)
- комната с клетками для животныхvoid method(Room<Cage<? extends Animal>>)
- комната, которая может содержать несколько видов клеток для животных. Например, комната может одновременно содержатьCage<Dog>
иCage<Animal>
,
Сейчас в add3(Room<Cage<? extends Animal>> room)
Вы запрашиваете последний тип помещения, в котором могут содержаться "всевозможные клетки для животных". Поэтому комната, переданная методу, может содержать или добавлять новые клетки для собак. room.add(new Cage<Dog>())
или любой другой тип клетки.
Однако, чтобы вызвать этот метод, вам нужно сначала создать новую "универсальную" комнату (которая поддерживает все клетки):
Room<Cage<? extends Animal>> room = new Room<Cage<? extends Animal>>();
add3(room);
Предоставление ему комнаты с собачьими клетками не сработает:
// Here we create a room that can contain only dog cages
Room<Cage<Dog>> room = new Room<Cage<Dog>>();
// But the method needs a "any kind of animal cage" room
// Therefore we get error during compilation
add3(room);
Если вы хотите написать более гибкий метод, который принимает комнаты, способные как минимум содержать клетки для собак, это может выглядеть так:
void add(Room<Cage<? super Dog>> room) {
room.add(new Cage<Dog>());
room.add(new Cage<Animal>());
}
При использовании списка неизвестного типа помечается ?
, список в основном только для чтения. Вы можете вставить только null
внутрь. Вы не можете использовать предметы в списке. Здесь вы имеете дело с неизвестным подклассом Animal
void add1(List<? extends Animal> list) {
list.add(new Dog());
}
Даже если Dog
это подкласс Animal
, List
из Dog
не является подклассом List
из Animal
, Java не знает этого автоматически, поэтому вы должны указать это вручную, как вы это делали в add3(..)
void add4(List<Cage<Animal>> list) {
list.add(new Cage<Dog>());
}
За add1
:
// We don't want to add a Dog to a room of cats...
Room<Cat> cats = new Room<Cat>();
add1(cats);
Cat cat = cats.get(0);
За add4
мы не хотим добавлять Cage<Dog>
к Room
из Cage<Animal>
... А Cage<Dog>
не является Cage<Animal>
хотя это Cage<? extends Animal>
, Это похоже на первый бросок, только с еще одним уровнем вложенности...