ServiceLoader where type parm is itself generic

1.5k views Asked by At
class ServiceLoader<S> implements Iterable<S> {
    // ...
}

interface Foo<T> {
    // ...
}

class FooRepository {
    void add(Iterable<Foo<?>> foos) {
        // ...
    }
}

FooRepository repo = new FooRepository();
repo.add(ServiceLoader.load(Foo.class));

This yields a compiler error: "The method add(Iterable<Foo<?>>) in the type FooRepository is not applicable for the arguments (ServiceLoader<Foo>)".

I was hoping to be able to treat an Iterable<Foo> as an Iterable<Foo<?>>, but on its own it doesn't seem to work. What would be the cleanest way to transmute the result of ServiceLoader.load(Foo.class) into something I can feed to FooRepository.add()?

3

There are 3 answers

1
Daniel Pryden On BEST ANSWER

If you're talking about java.util.ServiceLoader<S>, then its Javadoc says:

The provider class is typically not the entire provider itself but rather a proxy which contains enough information to decide whether the provider is able to satisfy a particular request together with code that can create the actual provider on demand. [...] The only requirement enforced by this facility is that provider classes must have a zero-argument constructor so that they can be instantiated during loading.

Combined with the way that the provider class is clearly instantiated using reflection, that tells me that a provider class declared with a generic type argument will in fact be instantiated as a raw type.

Thus, because you're passing in an object of type Class<Foo> -- and there's no way in Java to construct a object of type Class<Foo<Bar>> -- then what you get back is what you asked for, an Iterable<Foo>, where Foo is the raw type created from Foo<T>.

Unfortunately, I think the simple answer is: Don't do that then! Either make your provider class be a concrete subclass of the generic type, or else make your provider class be simply a factory that can construct new objects of the appropriate generic type.

Honestly, I can't think of any good reason to make a generic service provider class anyway -- where would you provide the type arguments? I think the factory mechanism is your best bet.

1
Shivan Dragon On

That's because generic types are for compile time only. They are erased at runtime (type erasure). The generic type T of the Foo interface will no longer be known at runtime, so the "add" method of the Repo class will not let you further (generically) type the argument type (will not let you have the argument of type Foo<?>, since it can't guarantee that inside the method you'll be able to actually treat that argument as an argument of type F<?>. It only knows that it's of type Foo, because that's an actual type, but it won't let you generically type it further. Your code will run if you change the method to

void add(Iterable<Foo> foos)

2
irreputable On

The blame falls on Java's type system. You need brute cast.

Either cast Foo.class to Class<Foo<?>>, or ServiceLoader<Foo> to ServiceLoader<Foo<?>>. Both are obviously harmless.