简体   繁体   中英

Generics Subtyping issue in java

I am trying to learn Subtyping in Java and I am not an better person in generics so I am getting this issue or doubt-

import java.util.ArrayList;
import java.util.Collection;

interface Animal<T>{}
class Lion implements Animal<Lion>{}
class Butterfly implements Animal<Butterfly>{}
class Cage<T>{
    public <T> void addAnimal(T t){

    }
}

interface CageAnimal<E> extends  Collection<E>{}
public class SubType<T> {

    public <T> SubType() {
        Lion lion = new Lion();
        Butterfly butterfly = new Butterfly();      
        /**
         * **Here inside Lion cage, we added Butterfly : WRONG**
         */
        Cage<Lion> cageLion = new Cage<Lion>();
        cageLion.addAnimal(lion);
        cageLion.addAnimal(butterfly);


        CageAnimal<Lion> cageAnimalLion = (CageAnimal<Lion>) new ArrayList<Lion>();
        cageAnimalLion.add(lion);

        //cageAnimalLion.add(butterfly);//Butterfly is Not Supposed to add here as it is the cage of Lion
    }   
}

In the above example when I declare Cage , why I am able to add Butterfly and in the Same case when I made CageAnimal type, I am not able to add any Buttefly

Cage<Lion> cageLion = new Cage<Lion>();
cageLion.addAnimal(lion);
cageLion.addAnimal(butterfly);

and in case of Cage

Cage<Animal> cageAnimalLion = new Cage<Lion>();
cageAnimalLion.addAnimal(lion);
cageAnimalLion.addAnimal(butterfly); //Throwing Compile Error

Declare Cage class like this:

class Cage<T extends Animal> {
    public void addAnimal(T t) { ... }
}

If you declare the addAnimal method in the following way...

public void <T> addAnimal(T t)

... you are "hiding" the T type parameter with a different type parameter with the same name. It is the same as if you declared the method like this:

class Cage<T extends Animal> {
    public void <X> addAnimal(X t) { ... }
}

...which is obviously not doing its job. On the other hand, in the first version I wrote, both the T in declaration of the class and the method are the same.

Moreover declaring <T extends Animal> bound ensures that the cage can only be of type that extends an Animal , ie Cage<Lion> , Cage<Butterfly> , but Cage<String> is illegal.

And of course, you cannot cast an ArrayList to CageAnimal , that will fail at runtime with a ClassCastException , because ArrayList in not a subtype of CageAnimal .

This line

 public <T> void addAnimal(T t){

should probably be

 public void addAnimal(T t){

Because CageAnimal and Cage are very different things. Looks how you've defined generic parameter for Cage :

public <T> void addAnimal(T t){

}

This <T> you put on the method, means that method has its own generic parameter, different from the one you've defined in class. If you remove it from method signature it will use generic parameter of the class.

Eg

public void addAnimal(T t)

Your problem is that fundamentally your Cage will accept any T , and therefore any Animal . The various T 's don't all refer to the same value of T, they're variables local to the class or method.

What you could write is something like this:

public class Cage<T> {
  public void addAnimal(Animal<T> caged) {
  }
}

Now you will at least get compiler errors in the common case of:

Cage<Lion> c=new Cage<Lion>();
c.add(new Butterfly()); // should error AFAIK

However it will be reduced to a warning in case of:

Animal butterfly=new Butterfly();
Cage<Lion> c=new Cage<Lion>();
c.add(butterfly); // warning about raw types... IIRC

Because, fundamentally Cage will still accept any Animal .

EDIT: Note that the earlier mentioned answer of removing the <T> local to the addAnimal method will work better for this purpose.

By declaring public <T> void addAnimal(T t) you're parameterising the method as well as the class Cage . This T has no relation to the T in Cage<T> .

You can either have:

class Cage<T extends Animal<T>> {
    public void addAnimal(T animal) {
    }
}

or, if you want the Animal returned then have:

class Cage<T extends Animal<T>> {
    public T addAnimal(T animal) {
    }
}
class Cage<T>{
    public <T> void addAnimal(T t){

    }
}

The Cage class has a generic method addAnimal. The generic type associated with the method causes the generic type associated with the class to be ignored and the type of the parameter to be used as the generic type for the method.

Try executing the following example to see what is happening:

public class TestCage {

    /**
     * @param args
     */
    public static void main(String[] args) {
        Cage<String> cage1 = new Cage<String>();
        cage1.addAnimal(new String("test1"));
        cage1.addAnimal(new Integer(1));
        cage1.addAnimal2(new String("test2"));
        //cage1.addAnimal2(new Integer(1));  //Uncomment to throw error
    }

}

class Cage<T>{
        public <T> void addAnimal(T t){
              System.out.println("T: " + t.getClass().getName());
        }

        public void addAnimal2(T t){
              System.out.println("T: " + t.getClass().getName());
        }
}

In summary, by adding a generic method to the class, the generic type parameter of the class is ignored and the type of the parameter passed into the method is used as the generic type parameter of the method.

尝试将<T>public <T> void addAnimal(T t)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM