0

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.

3
  • 2
    The part between <> has to be a type name, it can't be a variable or an expression that resolves to a reference. Commented May 2, 2014 at 15:26
  • @SotiriosDelimanolis Yea, sucks! Commented May 2, 2014 at 15:44
  • Are all the Animal classes (Yetti, Doge, etc) in the same package? Commented May 2, 2014 at 16:22

4 Answers 4

1

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


    }
Sign up to request clarification or add additional context in comments.

3 Comments

Yes they do, this does work and I had been using the same solution before, however it then poses a further problem - i've updated the question to demonstrate this!
Would you mind showing the actual code? Because what you wrote in the edit should be working (just did a quick poc to check it and it was), although your pseudo code is a bit mix of wildcards and generic arguments but I just assumed a typo, maybe I was wrong
Can't show the actual code for security reasons, but I managed to get it working by dropping the type params on the List, i.e. List<? extends Animal> animals -> List animals and it works fine... Java generics are so broken.
1

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;
}

Comments

1

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.

Comments

1

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;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.