3

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?

asked Mar 21, 2014 at 14:42
3
  • 1
    this is not looking right to my. think about an other solution. Commented Mar 21, 2014 at 14:45
  • I would like to, if I could think of one Commented 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. Commented Mar 21, 2014 at 14:47

2 Answers 2

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) 
answered Mar 21, 2014 at 15:11

Comments

1

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.

answered Mar 21, 2014 at 14:51

7 Comments

This method is fundamentally type-unsafe. The calling code can arbitrarily choose the return type.
Yes, but so is the solution with a Class parameter, since you can call like this DataSubclass1 s = Data.getInstance(DataSubclass1.class, "keyForDataSubclass2Instance"), which will throw runtime exception.
It will throw an exception as intended at the place where we explicitly make a runtime cast. It will not have heap pollution, i.e. expressions of a certain type will actually point to that type at runtime, and cause an exception somewhere else that doesn't have a cast.
The runtime cast is made in the getInstance method in return statement in both cases, so that's going to be the origin of exception in both cases.
Nope, in your method, there is no runtime cast in the getInstance method.
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.