I have an abstract class called Data, with a getInstance() method which should return instances of concrete subclasses of Data.
I want to pass a String to the getInstance method, and this string will define exactly what class will be returned.
So far I have:
public abstract class Data {
private Map<String, Data> instances;
public <T extends Data> T getInstance(Class<T> type, String dataName) {
return type.cast(instances.get(dataName));
}
}
Where the getInstance() method looks up the correct instance in the instances
map. I think this should be OK provided the map is populated (by Spring), but the caller must match the Class<T> type
parameter with the String dataName
parameter.
Is there any way I can remove the Class<T> type
parameter and not have generics warnings?
-
1this is not looking right to my. think about an other solution.Philipp Sander– Philipp Sander2014年03月21日 14:45:48 +00:00Commented Mar 21, 2014 at 14:45
-
I would like to, if I could think of oneNickJ– NickJ2014年03月21日 14:46:50 +00:00Commented Mar 21, 2014 at 14:46
-
What is your reasoning for using strings to identify types? If you can avoid it, do so. You're opening up your system to great risk at runtime by relying on strings - which cannot be compile-time checked - to identify concrete types.cfeduke– cfeduke2014年03月21日 14:47:42 +00:00Commented Mar 21, 2014 at 14:47
2 Answers 2
No, you will always get a warning if you try to cast into a generic type without knowing the runtime class of the type. And no, there is no way to get an instance of the generic class without providing it as an argument - generics are erased at runtime.
The only way to get no warnings without an explicit cast is to return either Object or Data (depending on you Map) and require the user to make the cast instead:
public Data getInstance(String dataName) {
return instances.get(dataName);
}
// OR
public Object getInstance(String dataName) {
return instances.get(dataName);
}
In my opinion it is best, to provide both methods for convenience: Data getInstance(String)
and <T extends Data> T getInstance(Class<T>, String)
. This is essentially also what OSGI does in the BundleContext class, so one is able to get service references, with the difference, that it is not possible to get services with arbitrary ids (the id is always the name of the class):
ServiceReference<?> BundleContext#getServiceReference(java.lang.String clazz)
ServiceReference<S> BundleContext#getServiceReference(java.lang.Class<S> clazz)
Comments
You can skip the class parameter altogether.
public <T extends Data> T getInstance(String dataName) {
return (T)instances.get(dataName);
}
This will still generate a warning (which you can suppress). Both ways will throw a runtime exception if actual class in the map differs from expected type. In my example compile type inferrence will not work in certain cases and you will need to specify type when calling like this: Data.<SubClass>getInstance("name");
It is possible to have a solution which will return null if the subtype of data for the key is not correct.
public <T extends Data> T getInstance(Class<T> clazz, String dataName) {
Data d = instances.get(dataName);
if (d != null && clazz.isAssignableFrom(d.getClass())) {
return (T)d;
} else {
return null;
}
}
This code will return null if the value in the map is not of correct class T
or its subclass.
7 Comments
Class
parameter, since you can call like this DataSubclass1 s = Data.getInstance(DataSubclass1.class, "keyForDataSubclass2Instance")
, which will throw runtime exception.getInstance
method in return statement in both cases, so that's going to be the origin of exception in both cases.getInstance
method.