7

I'm experimenting with the Java module system. I'm trying to use the ServiceLoader for generic interfaces. It works but I've got warnings in the module-info. Here is my minimal code

module testProvideWith {
  provides ServiceGeneric with SString;
  uses ServiceGeneric;
}
public interface ServiceGeneric<T> {
  T getT();
}
public class SString implements ServiceGeneric<String>{
  public String getT() {return "Hello";}
}

I'm not surprised to get a warning when I try

ServiceGeneric<String> serv=ServiceLoader.load(ServiceGeneric.class).findFirst().get();

I know about generic erasure, I understand that I would have to use annotations or some other trick to annotate the correct service. But... there is no mention at all of generic services that I can find. I expected to be able to write something like

module testProvideWith {
  provides ServiceGeneric<String> with SString;
  uses ServiceGeneric<String>;
  //more cases could be added
}
...
ServiceLoader.load(ServiceGeneric.class,String.class)

where the module and the loader cooperate to keep track of what generic version is available.. but... nothing... no trace of anyone ever considering this possibility... I'm I missing something?

Naman
  • 27,789
  • 26
  • 218
  • 353
Marco Servetto
  • 684
  • 1
  • 5
  • 14
  • 1
    Agreed there is no explicit mention of declaring generic type and loading such services with the Java module system. But seems like this have been assumed that one uses a service such that the type bound can be inferred/loaded by the module system as well. +1 for the question – Naman Jun 18 '19 at 07:28
  • Thinking more around this, the erasure might just be one of the reasons for not being able to specify the type of generic classes. – Naman Jun 28 '20 at 04:02

1 Answers1

0

The service provider isn't designed for generic services, as you discovered. However, you can workaround this limitation by encoding the generic argument in your service provider implementation, plus some managerial code.

Specifically, your service providers should yield the Class of their generic argument, such as via a getter method.

// I prefer to define a non-generic service for use with ServiceLoader. 
// Strictly speaking, this interface is auxiliary.
public interface ServiceBundle {
  List<ServiceGeneric<?>> providers();
}

public interface ServiceGeneric<T> {
  Class<T> type();

  T getT();
}

public class ServiceString implements ServiceGeneric<String>{
  public Class<String> type() {
    return String.class;
  }
  public String getT() {
    return "Hello";
  }
}

Then you should be able to iterate over ServiceLoader.load(ServiceBundle.class) and place your service providers into a Map<Class<?>, ServiceGeneric<?>> for later use.

Map<Class<?>, ServiceGeneric<?>> providers = new HashMap<>();
for (ServiceBundle bundle : ServiceLoader.load(ServiceBundle.class)) {
  for (ServiceGeneric<?> provider : bundle.providers()) {
    providers.put(provider.type(), provider);
  }
}
providers = Map.copyOf(providers); // freeze the map

//
// ...
//

public class GenericServiceManager {

  public <T> ServiceGeneric<T> getProvider(Class<T> type) {
    // Assuming everything is set up correctly, this cast will be safe
    return providers.get(type);
  }
}
A248
  • 690
  • 7
  • 17