NickJ
NickJ

Reputation: 9579

Java Generics - get instance of class

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?

Upvotes: 3

Views: 3740

Answers (2)

RokL
RokL

Reputation: 2812

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.

Upvotes: 1

Balder
Balder

Reputation: 8718

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) 

Upvotes: 2

Related Questions