56

I would like to be able to inject a generic implementation of a generic interface using Guice.

public interface Repository<T> {
  void save(T item);
  T get(int id);
}

public MyRepository<T> implements Repository<T> {
  @Override
  public void save(T item) {
    // do saving
    return item;
  }
  @Override
  public T get(int id) {
    // get item and return
  }
}

In C# using Castle.Windsor, I'd be able to do:

Component.For(typeof(Repository<>)).ImplementedBy(typeof(MyRepository<>))

but I don't think the equivalent exists in Guice. I know I can use TypeLiteral in Guice to register individual implementations, but is there any way to register them all at once like in Windsor?

Edit:

Here's an example of usage:

Injector injector = Guice.createInjector(new MyModule());
Repository<Class1> repo1 = injector.getInstance(new Key<Repository<Class1>>() {});
Repository<Class2> repo2 = injector.getInstance(new Key<Repository<Class2>>() {});

Although the more likely usage would be injection into another class:

public class ClassThatUsesRepository {
  private Repository<Class1> repository;

  @Inject
  public ClassThatUsesRepository(Repository<Class1> repository) {
    this.repository = repository;
  }
}
Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
Sean Carpenter
  • 7,681
  • 3
  • 37
  • 38
  • Could you add a snippet showing how you would like to _use_ this? – Thorbjørn Ravn Andersen Nov 21 '10 at 16:51
  • 2
    I'm with you, I want to do the same thing. Everybody should have this problem. There must be something they're not telling us. :) – PapaFreud Mar 22 '11 at 10:18
  • I'd like to know the solution too, i know nothing about C#, but obvious the C# way is much more modern. – Mike Mar 01 '12 at 13:02
  • 1
    Still no solution available ? Repeating the bindings for all possible generic values is a waste of time. Sure in some special case you might actually want a different implementation, but it should not be the default. – David Nouls Nov 04 '14 at 11:42

4 Answers4

80

In order to use generics with Guice you need to use the TypeLiteral class to bind the generic variants. This is an example of how you're Guice injector configuration could look like:

package your-application.com;

import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;

public class MyModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(new TypeLiteral<Repository<Class1>>(){})
      .to(new TypeLiteral<MyRepository<Class1>>(){});
  }
}

(Repository is the generic interface, MyRepository is the generic implementation, Class1 is the specific class used in the generics).

Ruslan Batdalov
  • 793
  • 1
  • 8
  • 21
Kdeveloper
  • 13,679
  • 11
  • 41
  • 49
  • 6
    This is how I'm doing it. What I was hoping to do is to eliminate the need to register each individual implementation (MyRepository, MyRepository, etc.). That's what the Windsor example does. – Sean Carpenter Nov 21 '10 at 20:21
  • 2
    Sorry about that, I should have read your question more carefully. I have been looking into that type of Guice generics usage my selves, but I could not solve it either. I guess one way to solve it would be to extend Guice and write your own Module(helper). With the use of the Java reflection API you could find all Injection variants and bind them. – Kdeveloper Nov 22 '10 at 00:18
4

Generics not being retained at run-time sure made it harder to grasp the concept at first. Anyways, there are reasons new ArrayList<String>().getClass() returns Class<?> and not Class<String> and although its safe to cast it to Class<? extends String> you should remember that generics are there just for compile-time type checks (sort of like implicit validation, if you will).

So if you want to use Guice to inject MyRepository (with any type) implementation whenever you need a new instance of Repository (with any type) then you don't have to think about generics at all, but you're on your own to ensure type safety (that's why you get those pesky "unchecked" warning).

Here is an example of code working just fine:

public class GuiceTest extends AbstractModule {

    @Inject
    List collection;

    public static void main(String[] args) {
        GuiceTest app = new GuiceTest();
        app.test();
    }

    public void test(){
        Injector injector = Guice.createInjector(new GuiceTest());
        injector.injectMembers(this);

        List<String> strCollection = collection;
        strCollection.add("I'm a String");
        System.out.println(collection.get(0));

        List<Integer> intCollection = collection;
        intCollection.add(new Integer(33));
        System.out.println(collection.get(1));
    }

    @Override
    protected void configure() {
        bind(List.class).to(LinkedList.class);
    }
}

This prints:

I'm a String
33

But that list is implemented by a LinkedList. Although in this example, if you tried to asign an int something that is String you would get an exception.

int i = collection.get(0)

But if you want to get an injectable object already type-casted and dandy you can ask for List<String> instead of just List, but then Guice will treat that Type variable as part of the binding key (similar to a qualifier such as @Named). What this means is that if you want injection specifically List<String> to be of ArrayList<String> implementation and List<Integer> to be of LinkedList<Integer>, Guice lets you do that (not tested, educated guess).

But there's a catch:

    @Override
    protected void configure() {
        bind(List<String>.class).to(LinkedList<String>.class); <-- *Not Happening*
    }

As you might notice class literals aren't generic. That's where you use Guice's TypeLiterals.

    @Override
    protected void configure() {
        bind(new TypeLiteral<List<String>>(){}).to(new TypeLiteral<LinkedList<String>>(){});
    }

TypeLiterals retain the generic type variable as part of meta-information to map to desired implementation. Hope this helps.

SGal
  • 1,072
  • 12
  • 13
2

You can use (abuse?) the @ImplementedBy annotation to make Guice generate generic bindings for you:

@ImplementedBy(MyRepository.class)
interface Repository<T> { ... }

class MyRepository<T> implements Repository<T> { ... }

As long as just-in-time bindings are enabled, you can inject Repository<Whatever> without any explicit binding:

    Injector injector = Guice.createInjector();
    System.out.println(injector.getBinding(new Key<Repository<String>>(){}));
    System.out.println(injector.getBinding(new Key<Repository<Integer>>(){}));

The catch is that the target of the binding is MyRepository, rather than MyRepository<T>:

LinkedKeyBinding{key=Key[type=Repository<java.lang.String>, annotation=[none]], source=interface Repository, scope=Scopes.NO_SCOPE, target=Key[type=MyRepository, annotation=[none]]}
LinkedKeyBinding{key=Key[type=Repository<java.lang.Integer>, annotation=[none]], source=interface Repository, scope=Scopes.NO_SCOPE, target=Key[type=MyRepository, annotation=[none]]}

That's usually not a problem, but it means that MyRepository can't inject a TypeLiteral<T> to figure out its own type at runtime, which would be particularly useful in this situation. Aside from that, to the best of my knowledge, this works fine.

(If someone feels like fixing this, I'm pretty sure it would just require some extra calculations around here to fill in the target type parameters from the source key.)

jedediah
  • 1,179
  • 9
  • 20
0

Somewhat related, hopefully someone will find this useful. In some cases, especially when you have the java.lang.Class instance of the type you want to generalize, it's possible to force an injection in runtime by extending the ParameterizedType class.

In the solution below, a factory method creates a generic Collection< ? extends Number> and Map < K,V > given an instance of the class object

Example.java:

@SuppressWarnings("unchecked")
public class Example<K extends Number> {

  Injector injector = ...

  public Set<K> foo(Class<K> klass) {
    CompositeType typeLiteral = new CompositeType(Set.class, klass);
    Set<K> set = (Set<K>) injector.getInstance(Key.get(typeLiteral));
    return set;
  }

  public <V> Map<K,V> bar(Class<K> keyClass, Class<V> valueClass) {
    CompositeType typeLiteral = new CompositeType(Map.class, keyClass, valueClass);
    Map<K,V> collection = (Map<K,V>) injector.getInstance(Key.get(typeLiteral));
    return collection;
  }
}

CompositeType.java:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;

public class CompositeType implements ParameterizedType {

  private final String typeName;
  private final Class<?> baseClass;
  private final Type[] genericClass;

  public CompositeType(Class<?> baseClass, Class<?>... genericClasses) {
    this.baseClass = baseClass;
    this.genericClass = genericClasses;
    List<String> generics = ((List<Class<?>>)Arrays.asList(genericClasses))
            .stream()
            .map(Class::getName)
            .collect(Collectors.toList());
    String genericTypeString = StringUtils.join(generics, ",");
    this.typeName = baseClass.getName() + "<" + genericTypeString + ">";
  }

  @Override
  public String getTypeName() {
    return typeName;
  }

  @Override
  public Type[] getActualTypeArguments() {
    return genericClass;
  }

  @Override
  public Type getRawType() {
    return baseClass;
  }

  @Override
  public Type getOwnerType() {
    return null;
  }
}
ohad serfaty
  • 644
  • 6
  • 11