I have a "strange" problem about Java generics.
First I list my code:
Service.class
package jse.generics.service;
public interface Service {
}
ServiceProvider.class
package jse.generics.service;
public interface ServiceProvider<T extends Service> {
public T getService();
}
ServiceProviderRegistry.class
package jse.generics.service;
import java.util.HashMap;
import java.util.Map;
public class ServiceProviderRegistry<T extends Service> {
private Map<Class<T>, ServiceProvider<T>> map = new HashMap<Class<T>, ServiceProvider<T>>();
public void register(Class<T> clazz, ServiceProvider<T> provider) {
map.put(clazz, provider);
}
}
FooService.class
package jse.generics.service;
public class FooService implements Service {
}
FooServiceProvider.class
package jse.generics.service;
public class FooServiceProvider implements ServiceProvider<FooService> {
@Override
public FooService getService() {
return new FooService();
}
}
ServiceTest.class
package jse.generics.service;
public class ServiceTest {
/**
* @param args
*/
public static void main(String[] args) {
ServiceProviderRegistry<? extends Service> registry = new ServiceProviderRegistry<Service>();
registry.register(FooService.class, new FooServiceProvider());
}
}
In the ServiceTest class the compiler complaints that the registry.register method is not applicable for arguments passed to it.I really don't know why this happens. So I am looking forward to you guy's help to solve this problem.
In this case you would be better off if ServiceProviderRegistry
were not a parameterized class, but instead make the register
method (and presumably the corresponding lookup method) generic. In your current approach a ServiceProviderRegistry<Service>
can only register Service.class
, not any subclasses of Service.
All you really care about is that the class and the provider passed to register
match each other, which is an ideal case for a generic method.
public class ServiceProviderRegistry {
private Map<Class<?>, ServiceProvider<?>> registry = new HashMap<>();
public <T extends Service> void register(Class<T> cls, ServiceProvider<T> provider) {
registry.put(cls, provider);
}
@SuppressWarnings("unchecked")
public <T extends Service> ServiceProvider<T> lookup(Class<T> cls) {
return (ServiceProvider<T>)registry.get(cls);
}
}
You will need the @SuppressWarnings
annotation - it's impossible to implement this pattern without one in a way that will completely satisfy the compiler, which only has access to the compile-time types. In this case you know the cast will always be safe at runtime because register
is the only thing that modifies the registry
map, so the @SuppressWarnings
is justified. Jon Skeet's answer to this related question sums it up very nicely
Sometimes Java generics just doesn't let you do what you want to, and you need to effectively tell the compiler that what you're doing really will be legal at execution time.
Method signature is
public void register(Class clazz, ServiceProvider provider)
You are passing two Class
instance here :
registry.register(FooService.class, FooServiceProvider.class);
You need to pass an instance of a class which implements the ServiceProvider
interface as the second argument to the register()
method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With