Can generics allow the Java compiler to check the type of keys and values in a map?

橙三吉。 提交于 2020-01-15 06:45:13

问题


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...


回答1:


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();
  }

}



回答2:


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);
    }
}


来源:https://stackoverflow.com/questions/1473444/can-generics-allow-the-java-compiler-to-check-the-type-of-keys-and-values-in-a-m

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!