views:

99

answers:

2

Is it possible to tie nested generics/captures together?

I often have the problem of having a Map lookup of class to genericized item of said class. In concrete terms I want something like this (no, T is not declared anywhere).

private Map<Class<T>, ServiceLoader<T>> loaders = Maps.newHashMap();

In short, I want loaders.put/get to have semantics something like these:

<T> ServiceLoader<T> get(Class<T> klass) {...}
<T> void put(Class<T> klass, ServiceLoader<T> loader) {...}

Is the following the best I can do? Do I have to live with the inevitable @SuppressWarnings("unchecked") somewhere down the line?

private Map<Class<?>, ServiceLoader<?>> loaders = Maps.newHashMap();
+6  A: 

Let me see If I got your intention: you want a map that stores pairs of Class/ServiceLoader where each pair is parameterized by the same T, but T may be different across pairs?

If this is the case then the best solution is to declare your own class which will exhibit such an interface. Internally it will store these pairs in a generic Map<Class<?>,ServiceLoader<?>> map.

public class MyMap {
   private Map<Class<?>, ServiceLoader<?>> loaders 
      = new HashMaps<Class<?>, ServiceLoader<?>>();

   public<T> void put(Class<T> key, ServiceLoader<T> value) {
      loaders.put(key, value);
   }

   @SuppressWarnings("unchecked")
   public<T> T get(Class<T> key) { return (ServiceLoader<T>) loaders.get(key); }
}

@SuppressWarnings("unchecked") annotations are not pure evil. You should try to avoid them but there are certain cases where you can figure out that the cast is correct despite the fact that the compiler cannot see that.

Itay
+1, but key.cast() would only work if the value type is T. Josh Bloch called this "Typesafe Heterogeneous Container Pattern" in "Effective Java". I usually write loaders.put(key,key.cast(value)) in the put method, then if user somehow bypasses compile-time checks, the error would still pop up when the value is added, rather then when it's retrieved.
Yardena
@Yardena - You're absolutely right. I overlooked this point. @SuppressWarnings cannot be avoided after all
Itay
A: 

My suggestion is to create a new Object for such case. I see you were using Maps.newHashMap() so I take it that you used Google Guava so I will use ForwardingMap.

public class Loader<T> extends ForwardingMap<Class<T>, ServiceLoader<T>> {

   private Map<Class<T>, ServiceLoader<T>> delegate = Maps.newHashMap();

}

A simple test proved that my suggestion is working:

public class Loader<T> extends ForwardingMap<Class<T>, Class<T>> {

   private Map<Class<T>, Class<T>> delegate = Maps.newHashMap();

   @Override protected Map<Class<T>, Class<T>> delegate() {
      return delegate;
   }

   public static void main(String[] args) {
      Loader<Integer> l = new Loader<Integer>();

      l.put(Integer.class, Integer.class);

      // error
      l.put(Integer.class, String.class);
   }

}
nanda
I wanted T to be the same across Key/Value pairs; not across the whole collection.
Michael Deardeuff