tags:

views:

159

answers:

2

I am in a situation where I want to have a map where the keys are an interface class, and the corresponding value is a class which implements that interface. In other words the key and value type is related.

My current implementation of the method which adds to the map, and gets an instance of the implementation class looks like:

// should be something like Class<T>, Class<? extends T>
static Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>> ();

public static <T> void add(Class<T> interfaceT,
     Class<? extends T> implementationT) {

  map.put(interfaceT, implementationT);
}

public static <T> T get(Class<T> interfaceT) {
  // cast caused by definition not complete.

  Class<T> implementationT = (Class<T>) map.get(interfaceT);

  // try catch stuff omitted
  T t = implementationT.newInstance();
  return t;
 }

My question is:

Can I define the "map" variable so the cast in the get(...) method is unneeded? I could not make the " new HashMap<Class<T>, Class<? extends T>>()' work, so either it is impossible or I missed something fundamental :)

Please advise :)


Edit: It turned out that the asSubclass() method on Class did what I wanted :D

Class<?> rawClassFromMap = map.get(interfaceT);
Class<? extends T> implementationT = rawClassFromMap.asSubclass(interfaceT);

It is fine that implementationT is of type "? extends T" as I just need a T object returned.

I like generics. Reminds me of Haskell...

+4  A: 

It looks like the goal is something like the "Typesafe Heterogenous Container" described by Josh Bloch in Chapter 5 of Effective Java (item 29). In his case, he's mapping a type (Class<T>) to an (already-instantiated) instance (T).

You can do something similar, using asSubclass instead of cast:

final class Factory
{

  private Map<Class<?>, Class<?>> map = new HashMap<Class<?>, Class<?>>();

  <T> void map(Class<T> type, Class<? extends T> impl)
  {
    map.put(type, impl.asSubclass(type));
  }

  private <T> Class<? extends T> get(Class<T> type)
  {
    Class<?> impl = map.get(type);
    if (impl == null) 
      throw new IllegalArgumentException("Unknown type: " + type);
    return impl.asSubclass(type);
  }

  <T> T create(Class<T> type) 
    throws Exception
  {
    Class<? extends T> impl = get(type);
    Constructor<? extends T> ctor = impl.getConstructor();
    return ctor.newInstance();
  }

}
erickson
Thanks. Your solution was right on the spot.I'll reread the generics.pdf now - this is a rather steep learning curve :)
Thorbjørn Ravn Andersen
A: 

I would suggest a Proxy. Here's the Java example.

public interface Bike {

    public String getWheels();

    public int getSize();

}

public class MountainBike implements Bike {

    @Override
    public int getSize() {
     return 24;
    }

    @Override
    public String getWheels() {
     return "Treaded";
    }

    @Override
    public String toString() {
     String newLine = System.getProperty("line.separator");
     StringBuilder sb = new StringBuilder();
     sb.append("Type:   MOUNTAIN").append(newLine);
     sb.append("Wheels: ").append(getWheels()).append(newLine);
     sb.append("Size:   ").append(getSize()).append(newLine);
     return sb.toString();
    }

}

public class CruiserBike implements Bike {

    @Override
    public int getSize() {
     return 26;
    }

    @Override
    public String getWheels() {
     return "Smooth";
    }

    @Override
    public String toString() {
     String newLine = System.getProperty("line.separator");
     StringBuilder sb = new StringBuilder();
     sb.append("Type:   CRUISER").append(newLine);
     sb.append("Wheels: ").append(getWheels()).append(newLine);
     sb.append("Size:   ").append(getSize()).append(newLine);
     return sb.toString();
    }

}

public class BikeProxy implements InvocationHandler {

    private Object obj;

    public static Object newInstance(Object obj) 
    {
     return java.lang.reflect.Proxy.newProxyInstance(obj.getClass()
       .getClassLoader(), obj.getClass().getInterfaces(),
       new BikeProxy(obj));
    }

    public static <T> T newInstance(String className) 
    {
     try 
     {
      return (T) newInstance(Class.forName(className));
     } 
     catch (ClassNotFoundException e) 
     {
      throw new RuntimeException(e);
     }
    }

    public static <T> T newInstance(Class<T> bikeClass) 
    {
     try
     {
     return (T) java.lang.reflect.Proxy.newProxyInstance(Bike.class.getClassLoader(), new Class[]{Bike.class},
       new BikeProxy(bikeClass.newInstance()));
     }
     catch (Exception e)
     {
      throw new RuntimeException(e);
     }
    }

    private BikeProxy(Object obj) 
    {
     this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args)
      throws Throwable 
    {
     Object result;
     try 
     {
      result = m.invoke(obj, args);
     } 
     catch (InvocationTargetException e) 
     {
      throw e.getTargetException();
     } 
     catch (Exception e) 
     {
      throw new RuntimeException(e);
     }
     return result;
    }
}

public class ProxyTester 
{
    public static void main(String[] args) 
    {
     Bike mountainBike = BikeProxy.newInstance(MountainBike.class);
     System.out.println(mountainBike);

     Bike mountainBike2 = BikeProxy.newInstance(MountainBike.class.getName());
     System.out.println(mountainBike2);

     Bike cruiserBike = BikeProxy.newInstance(CruiserBike.class);
     System.out.println(cruiserBike);

     Bike cruiserBike2 = BikeProxy.newInstance(CruiserBike.class.getName());
     System.out.println(cruiserBike2);
    }
}
Droo