Путаница с коллекциями вложенных генериков

Пожалуйста, помогите мне понять, почему 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>, Это похоже на первый бросок, только с еще одним уровнем вложенности...

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