2

Please help me understand why add1() and add4() report errors and why add2() and add3() don't. Specifically, please show examples of undesired consequences if the compiler allowed each of these to compile.

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>());
   }
}
Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246

3 Answers3

6

In the method void add1(Room<? extends Animal> room), you define that the method accepts a Room that holds an Animal. For example, it can be Room<Cat>, or Room<Dog>--even Room<Animal> for holding all types of animals. However, keep in mind that the room has been created outside this method call and you can't make any assumptions about the room type other than that it holds either a specific 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

But once you're inside the method, you can't know specifically which type of room had been passed.

It would be valid to call the method with a room for only birds add1(new Room<Bird>()), as Bird does indeed extend Animal. However in the method body, you are adding a Dog into it. That's why it's invalid, we can't put Dog objects into Room<Bird>. It is a Room of some kind of animals and not a Room of any kind of animals.

If you wanted to write a method that added a dog to a room appropriate for adding dogs (but not limited to just dog-only rooms), you'd write it with signature addDogToRoom(Room<? super Dog> room) per this answer. This method could accept Room<Animal> as well as Room<Dog> and still within the method add new dogs to the room.

As about add4, it's the same but the opposite. With Room<Cage<Animal>> you specify that the method requires a specific room type--a room that allows only cages that hold any kind of Animal. But then you are trying to put a Cage<Dog> into it, a cage that allows only dogs. Therefore, it's invalid again.

Addition regarding comment:

Let's say there are cages designed for containing cats Cage<Cat> and cages designed for containing dogs Cage<Dog>. There are also universal cages, which can contain any kind of animal Cage<Animal>. Those are three different kinds of cages, they can't be substituted for one another, as they have totally different architecture and design.

  • void method(Cage<Dog>) means that the method needs one dog cage.
  • void method(Cage<Animal>) means that the method needs one universal cage.
  • void method(Cage<? extends Animal>) means that the method needs any kind of animal cage. Either dog cage, cat cage or universal cage.

Rooms are another level of abstraction--visualize them as a rooms with cages inside. There can be a room for storing cat cages Room<Cage<Cat>>, a room for storing dog cages Room<Cage<Dog>>, a room for storing universal cages Room<Cage<Animal>> and a room for storing multiple kinds of animal cages Room<Cage<? extends Animal>>. Therefore, the same rules apply:

  • void method(Room<Cage<Dog>>) - room of dog cages
  • void method(Room<Cage<Cat>>) - room of cat cages
  • void method(Room<Cage<Animal>>) - room of animal cages
  • void method(Room<Cage<? extends Animal>>) - room that can contain multiple kinds of animal cages. E.g., the room could simultaneously contain a Cage<Dog>, and a Cage<Animal>.

Now, in add3(Room<Cage<? extends Animal>> room), you request a last kind of room, the one that can contain "all kinds of animal cages". Therefore the room passed to the method can contain or add new dog cages room.add(new Cage<Dog>()) or any other type of cage.

However, to call that method, you would need to first create a new "universal" room (which supports all cages):

Room<Cage<? extends Animal>> room = new Room<Cage<? extends Animal>>();
add3(room);

Giving it a room of dog cages will not work:

// 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); 

If you wanted to write a more flexible method that accepts rooms capable at minimum of holding dog cages, it could look like this:

void add(Room<Cage<? super Dog>> room) {
   room.add(new Cage<Dog>());
   room.add(new Cage<Animal>());
}
Community
  • 1
  • 1
bezmax
  • 25,562
  • 10
  • 53
  • 84
  • Thanks, so now I clearly understand why `add1()` shouldn't compile, but now I don't understand why `add3()` compiles correctly. – Jeff Axelrod Mar 27 '12 at 14:05
  • @glenviewjeff It was wrong, so I removed it to rewrite it using different wording. – bezmax Mar 27 '12 at 14:24
  • @glenviewjeff Ok, wrote detailed explanation on the subject. Hope that helps. – bezmax Mar 27 '12 at 14:40
  • Max I hope you don't mind that I heavily edited your answer rather than adding my own answer. Thanks very much--I learned a lot as a result of this question! – Jeff Axelrod Mar 29 '12 at 18:49
  • @glenviewjeff No problem and thanks for additions, maybe someone will also find it useful as you did :) – bezmax Mar 29 '12 at 21:12
1

For 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);

For add4, we don't want to add a Cage<Dog> to a Room of Cage<Animal>... A Cage<Dog> isn't a Cage<Animal>, although it is a Cage<? extends Animal>. It's similar to the first cast, just with one more level of nesting...

Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks, so now I clearly understand why `add1()` shouldn't compile, but now I don't understand why `add3()` compiles correctly. – Jeff Axelrod Mar 27 '12 at 14:05
1

When you use a list of unknown type, marked with ?, the list is pretty much read only. You can insert only null into it. You cannot consume the items in the list. Here you deal with an unknown subclass of Animal

void add1(List<? extends Animal> list) {
   list.add(new Dog());
}

Even though Dog is a subclass of Animal, a List of Dog is not a subclass of a List of Animal. Java doesn't know this automatically so you have to specify it by hand, like you did in add3(..)

void add4(List<Cage<Animal>> list) {
   list.add(new Cage<Dog>());
}
Adrian
  • 5,603
  • 8
  • 53
  • 85
  • Adrian, I'm not sure what you were trying to show with the added cast to `Animal` in your version of `add1`. Were you just showing the implicit cast? You know this version of `add1()` also won't compile, right? – Jeff Axelrod Mar 27 '12 at 14:32
  • 1
    @glenviewjeff What he says is that if your method argument is defined as `Cage extends Animal>` (cage for some type of animal), you can not be sure which kind of cage you received. Therefore, you can't be sure which kind of animal you can put inside. Therefore, Java will not allow you to put anything inside, unless you cast it to some kind of _specific_ cage. For example: `((Cage)receivedCage).put(new Dog())`. – bezmax Mar 27 '12 at 14:49
  • I like that you explained the read-only dimension of `add1`. It's read-only because there's no way to determine what the actual type is of the list. However, I don't understand what you're getting at in pasting `add4` unmodified. Did you mean to change something to cause it to compile? – Jeff Axelrod Mar 27 '12 at 18:06
  • @glenviewjeff no I meant to reply to the code he posted so I included the code. I was hoping it would make my reply more clear because you see to what part of the code I'm referring to. – Adrian Mar 27 '12 at 19:21