Reputation: 11307
I cannot figure out the proper way to do this with generics. I have a class, Foo
, which has a generic (Foo<class>
). I then want to have a map of Map<String, Foo>
. Now, I can do this, and add Foo<String>
as one map item, and Foo<Integer>
as another. However, when I use the map's get method, I just get a Foo
back and the type can no longer be inferred, so if I do:
String s = map.get("StringFoo")
I get a compile error and have to do:
String s = (String) map.get("StringFoo")
What is a good pattern for doing something like this to avoid the casting, since that is what generics are for in the first place. I may be able to do something like Map<String, Foo<?>>
, but is that the way to do it?
Details of my code follow, this can be put into a directory and javac *.java && java Main
will run it.
I have a generic java class in Foo.java,
public class Foo<T>
{
T value;
public T getValue()
{
return this.value;
}
public void setValue(T t)
{
this.value = t;
}
}
Now, I have the following test class in Main.java:
import java.util.Map;
import java.util.HashMap;
public class Main
{
public static void main(String[] a)
{
Foo<String> fooStr = new Foo<String>();
fooStr.setValue("TEST 123");
Foo<Integer> fooInt = new Foo<Integer>();
fooInt.setValue(314159);
Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();
mapOfFoo.put("Strings", fooStr);
mapOfFoo.put("Integer", fooInt);
System.out.println("All set");
String s = mapOfFoo.get("Strings").getValue();
System.out.println("Got: " + s);
}
}
When I compile this, I get the following error:
Main.java:21: error: incompatible types
String s = mapOfFoo.get("Strings").getValue();
^
required: String
found: Object
1 error
When I do this in Main.java, it works:
import java.util.Map;
import java.util.HashMap;
public class Main
{
public static void main(String[] a)
{
Foo<String> fooStr = new Foo<String>();
fooStr.setValue("TEST 123");
Foo<Integer> fooInt = new Foo<Integer>();
fooInt.setValue(314159);
Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();
mapOfFoo.put("Strings", fooStr);
mapOfFoo.put("Integer", fooInt);
System.out.println("All set");
String s = (String)mapOfFoo.get("Strings").getValue();
System.out.println("Got: " + s);
}
}
I'm not sure what the best practice is for something like this. Does anyone have any suggestions?
Upvotes: 0
Views: 378
Reputation: 3344
This kind of construct is actually not uncommonly needed in Java, the problem is that generic parameters are a compile-time-only feature, and that map-retrieval is a run-time-only feature, so there's no way for the compiler to understand your rule and not raise a warning about it somewhere.
The safest way to implement a structure like this is through a wrapper class, which logically guarantees type safety, and all you have to do is throw a SuppressWarnings("unchecked")
on the getter.
public class FooMapper {
private Map<Class<?>, Foo<?>> fooMap = new HashMap<>();
public <T> void setFoo(Class<T> clazz, Foo<T> foo) {
fooMap.put(clazz, foo);
}
@SuppressWarnings("unchecked")
public <T> Foo<T> getFoo(Class<T> clazz) {
return (Foo<T>) fooMap.get(clazz);
}
}
This wrapper guarantees that you can never put a Foo
of the wrong type with the wrong Class
object, so the 'unchecked' cast can never possibly fail.
Upvotes: 3
Reputation: 285430
You appear to be using a non-generic Foo in your Map declaration and construction. Instead this:
Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();
Should be this:
Map<String, Foo<String>> mapOfFoo = new HashMap<String, Foo<String>>();
Then you won't have to cast to String -- one of the reasons to use generics in the first place, and Foo#getValue()
will return a String unequivocally.
Upvotes: 4