Domanda

I'm trying to write an AnimalGroup factory class that returns an instance which contains various different types of Animal. Unfortunately i'm being forced to duplicate code due to what seems like Java generics limitations. I've tried every possible combination of wildcards etc that I can think of with no luck.

Here's the code:

public AnimalGroup<?> getAnimalGroup (String animalName) {
    if(animalName.equals("Yetti")) {
        AnimalGroup<Yetti> animalGroup = new AnimalGroup<>(Yetti.class);
        animalGroup.doSomeProcessing();
        return animalGroup;
    }
    else {
        AnimalGroup<Doge> animalGroup = new AnimalGroup<>(Doge.class);
        animalGroup.doSomeProcessing();
        return animalGroup;
    }
}

Here's what I want to do:

public AnimalGroup<?> getAnimalGroup (String animalName) {
    Class<?> animalClass = Doge.class;

    if(animalName.equals("Yetti")) {
        animalClass = Yetti.class;
    }

    AnimalGroup<animalClass> animalGroup = new AnimalGroup<>(animalClass);
    animalGroup.doSomeProcessing();
    return animalGroup;
}

Update:

Using the <? extends Animal> solution, the following additional processing code no longer works:

// Pseudo-ish code
public <T extends Animal> void setAnimals (T animals) { this.animals = animals; }

List<? extends Animal> animals = getAnimals(animalClass);
animalGroup.setAnimals(animals);

The error given is pretty confusing:

setAnimals(java.util.List<capture<? extends Animal>>) in AnimalGroup cannot be applied to (java.util.List<capture<? extends Animal>>)

Any help much obliged. Thanks! Ben.

È stato utile?

Soluzione

Are Doge and Yeti extending the same Animal class? If yes then you could do

 public AnimalGroup<? extends Animal> getAnimalGroup (String animalName) {
    Class<? extends Animal> animalClass = Doge.class;

    if("Yetti".equals(animalName)) {
        animalClass = Yetti.class;
    }

    AnimalGroup<? extends Animal> animalGroup = new AnimalGroup<>(animalClass);
    animalGroup.doSomeProcessing();
    return animalGroup;
    }

If they not, then well, they should :)

I cannot really understand the edit I did a POC and the following code works (assuming interface A and implementing classes B and C:

private static List<? extends A> generateObjects(boolean isItB) {
        if (isItB) {
            return new ArrayList<B>() {
                {
                    add(new B());
                }
            };
        } else {
            return new ArrayList<C>() {
                {
                    add(new C());
                }
            };
        }
    }

    private static void consumeObjects(List<? extends A> consuming) {
        for (A a : consuming) {
            a.doStuff();
        }
    }

    public static void main(String[] args) {
        List<? extends A> generatedBs = generateObjects(false);
        consumeObjects(generatedBs);


    }

Altri suggerimenti

You could try an enum:

class AnimalGroup<T> {

    AnimalGroup(Class<T> itsClass) {

    }

    public void doSomeProcessing() {

    }
}

class Yetti {

}

class Dog {

}

// Connects the name of the animal with the 
enum Animal {

    Dog(Dog.class),
    Yetti(Yetti.class),
    // Can also have aliases this way.
    AbominableSnowman(Yetti.class);
    final Class itsClass;

    Animal(Class c) {
        itsClass = c;
    }
}

public AnimalGroup<?> getAnimalGroup(String animalName) {
    Animal animal = Animal.valueOf(animalName);
    AnimalGroup<?> g = new AnimalGroup<>(animal.itsClass);
    g.doSomeProcessing();
    return g;
}

You can try to do it without String classnames with pure generic using parameterized generic methods like this:

class Wrapper {

   public <T> AnimalGroup<T> getAnimalGroup (Class<T> cl) {   

       AnimalGroup<T> animalGroup = new AnimalGroup<T>(cl);
       animalGroup.doSomeProcessing();
       return animalGroup;               
   }
}

Then you AnimalGroup and Animal classes might look like this:

   class AnimalGroup<T> {
       private Class<T> cl;

       public AnimalGroup(Class<T> cl) {
           this.cl = cl;
       }

       public void doSomeProcessing() {
           System.out.println(cl);
        /*
        here i just printout the classname, but you can do proccessing like this
        if (cl.equals(Yetti.class)) do one thing, else do another, etc.            
        */
       }
    }

    class Yetti{}

    class Dog{}

And a simple test:

public class HelloWorld{

     public static void main(String []args){
        Wrapper an = new Wrapper();
        an.getAnimalGroup(Yetti.class);
        //new AnimalGroup<String>(String.class).doSomeProcessing();

     }
}

which returns following output

class Yetti

As you see, your animals are distinquished based on their types stored in Class cl variable of AnimalGroup class.

Here's a solution using a lookup Map<String, Class<? extends Animal>>:

private static Map<String, Class<? extends Animal>> map = new HashMap<String, Class<? extends Animal>>() {
    {
        put("Yetti", Yetti.class);
        put("Doge", Doge.class);
    }
};

public static AnimalGroup<? extends Animal> getAnimalGroup(String animalName) {
    AnimalGroup<? extends Animal> animalGroup = new AnimalGroup<>(map.get(animalName));
    animalGroup.doSomeProcessing();
    return animalGroup;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top