Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generic class's method is not applicable for the arguments passed

Tags:

java

generics

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.

like image 708
Samuel Gong Avatar asked Jun 07 '13 07:06

Samuel Gong


2 Answers

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.

like image 194
Ian Roberts Avatar answered Nov 14 '22 17:11

Ian Roberts


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.

like image 5
AllTooSir Avatar answered Nov 14 '22 18:11

AllTooSir