开发者

Useful example with super and obscurity with extends in Generics?

I know that there are a lot of questions about this topic, but unfortunately they couldn't help m开发者_运维技巧e to eliminate my obscurities. First of all, look at the following example. I don't understand, why the following "add"-method someCage.add(rat1) doesn't work and aborts with the following exception:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: The method add(capture#2-of ? extends Animal) in the type Cage is not applicable for the arguments (Rat)

Is this the same reason why Cage<Rat> is not a Cage<Animal>? If yes, I don't understand it in this example, so I'm not sure what the compiler exactly does. Here is the code example:

package exe;

import cage.Cage;
import animals.Animal;
import animals.Ape;
import animals.Lion;
import animals.Rat;

public class Main {

    public static void main(String[] args) {
        Lion lion1 = new Lion(true, 4, "Lion King", 8);
        Lion lion2 = new Lion(true, 4, "King of Animals", 9);
        Ape ape1 = new Ape(true, 2, "Gimpanse", true);
        Ape ape2 = new Ape(true, 2, "Orang Utan", true);
        Rat rat1 = new Rat(true, 4, "RatBoy", true);
        Rat rat2 = new Rat(true, 4, "RatGirl", true);
        Rat rat3 = new Rat(true, 4, "RatChild", true);

        Cage<Animal> animalCage = new Cage<Animal>();
        animalCage.add(rat2);
        animalCage.add(lion2);

        Cage<Rat> ratCage = new Cage<Rat>();
        ratCage.add(rat3);
        ratCage.add(rat1);
        ratCage.add(rat2);
//      ratCage.add(lion1); //Not Possible. A Lion is no rat

        Cage<Lion> lionCage = new Cage<Lion>();
        lionCage.add(lion2);
        lionCage.add(lion1);

        Cage<? extends Animal> someCage = new Cage<Animal>(); //? = "unknown type that is a subtype of Animal, possibly Animal itself"
        someCage = ratCage; //OK
//      someCage = animalCage; //OK
        someCage.add(rat1);  //Not Possible, but why?

        animalCage.showAnimals();
        System.out.println("\nRatCage........");
        ratCage.showAnimals();
        System.out.println("\nLionCage........");
        lionCage.showAnimals();
        System.out.println("\nSomeCage........");
        someCage.showAnimals();
    }
}

This is the cage class:

package cage;

import java.util.HashSet;
import java.util.Set;

import animals.Animal;

    public class Cage<T extends Animal> {  //A cage for some types of animals
        private Set<T> cage = new HashSet<T>();

        public void add(T animal)  {
            cage.add(animal);
        }

        public void showAnimals()  {
            for (T animal : cage) {
                System.out.println(animal.getName());
            }
        }
    }

Moreover, I would be pleased if you could give me a meaningful "super" example with this animal-cage-code. Until now I haven't understood how to use it. There are a lot of theoretical examples and I read about the PECS concept but anyhow I wasn't able to employ it in a meaningful matter yet. What would it mean to have a "consumer" (with super) in this example?


Example of super bound

The introduced transferTo() method accepts Cage<? super T> - a Cage that holds a superclass of T. Because T is an instanceof its superclass, it's OK to put a T in a Cage<? super T>.

public static class Cage<T extends Animal> { 
    private Set<T> pen = new HashSet<T>();

    public void add(T animal) {
        pen.add(animal);
    }

    /* It's OK to put subclasses into a cage of super class */
    public void transferTo(Cage<? super T> cage) {
        cage.pen.addAll(this.pen);
    }

    public void showAnimals() {
        System.out.println(pen);
    }
}

Now let's see <? super T> in action:

public static class Animal {
    public String toString() {
        return getClass().getSimpleName();
    }
}
public static class Rat extends Animal {}
public static class Lion extends Animal {}
public static class Cage<T extends Animal> { /* above */ }

public static void main(String[] args) {
    Cage<Animal> animals = new Cage<Animal>();
    Cage<Lion> lions = new Cage<Lion>();
    animals.add(new Rat()); // OK to put a Rat into a Cage<Animal> 
    lions.add(new Lion());
    lions.transferTo(animals); // invoke the super generic method
    animals.showAnimals();
}

Output:

[Rat, Lion]



Another important concept is that while it is true that:

Lion instanceof Animal // true

it is not true that

Cage<Lion> instanceof Cage<Animal> // false

It this were not the case, this code would compile:

Cage<Animal> animals;
Cage<Lion> lions;
animals = lions; // This assignment is not allowed
animals.add(rat); // If this executed, we'd have a Rat in a Cage<Lion>


You can add a Rat to a Cage<Rat> (of course).

You can add a Rat to a Cage<Animal>, because a Rat "is" an Animal (extends Animal).

You cannot add a Rat to a Cage<? extends Animal>, because <? extends Animal> might be <Lion>, which a Rat is not.

In other words:

Cage<? extends Animal> cageA = new Cage<Lion>(); //perfectly correct, but:
cageA.add(new Rat());  // is not, the cage is not guaranteed to be an Animal or Rat cage. 
                       // It might as well be a lion cage (as it is).
                       // This is the same example as in Kaj's answer, but the reason is not
                       // that a concrete Cage<Lion> is assigned. This is something, the
                       // compiler might not know at compile time. It is just that 
                       // <? extends Animal> cannot guarantee that it is a Cage<Rat> and 
                       // NOT a Cage<Lion>
//You cannot:
Cage<Animal> cageB = new Cage<Rat>(); //because a "rat cage" is not an "animal cage".
                                      //This is where java generics depart from reality.
//But you can:
Cage<Animal> cageC = new Cage<Animal>();
cageC.add(new Rat());  // Because a Rat is an animal.

Imagine having your Cage<? extends Animal> created by an abstract factory method, which gets implemented by a subclass. In your abstract base class you cannot tell which type actually gets assigned, neither can the compiler, because maybe the concrete class gets only loaded at runtime.

That means, the compiler cannot rely on Cage<? extends Animal> to not be a Cage of some other concrete subtype, which would make the assignment of a different subtype an error.


Both answers so far have been great. I'd just like to add a tidbit to help your understanding of them.

To further Ron's answer, you may be thinking the following:

"why is it that that someCage.add(rat1) becomes a Cage<? extends Animal>.add(rat1)? Can't someCage point to any Cage of any type which extends Animal (and I've now set it to point to a cage of rats?)"

Totally legitimate question. Thing is, when you do the someCage = ratCage, an element-by-element copy is done from ratCage into someCage. So in fact, you have not simply set someCage to now point to a ratCage. In actuality, someCage is still a Cage<? extends Animal>. You can't do someCage.add(rat1) because you don't know the type of the Cage, only that it's type is bounded above by Animal.

P.S.: You can't add anything to someCage since its type is unknown


I think your question can be answered by the following snippet of code:

Cage<? extends Animal> cage = new Cage<Lion>();
cage.add(rat1);  

You can clearly see that the code above shouldn't be valid, since you know that cage currently is a lion cage, and you shouldn't be allowed to add a rat to a lion cage.

The compiler does not what value you have assigned to the cage, so it can't allow cage.add(rat1) even if you assign a rat cage to the cage.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜