Путаница с коллекциями вложенных генериков - PullRequest
2 голосов
/ 27 марта 2012

Пожалуйста, помогите мне понять, почему 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 ]

6 голосов
/ 27 марта 2012

В методе 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>());
}
1 голос
/ 27 марта 2012

Когда вы используете список неизвестного типа, помеченный ?, список в значительной степени доступен только для чтения. Вы можете вставить только 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>());
}
1 голос
/ 27 марта 2012

Для 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> ... A Cage<Dog> не a Cage<Animal>, хотя это это a Cage<? extends Animal>. Это похоже на первое приведение, только с еще одним уровнем вложенности ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...