Reputation: 905
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.
Upvotes: 0
Views: 233
Reputation: 1133
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);
}
Upvotes: 1
Reputation: 424993
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;
}
Upvotes: 1
Reputation: 3219
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.
Upvotes: 1
Reputation: 65813
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;
}
Upvotes: 1