Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics: how to enforce restrictions between keys and values in a Map

Tags:

java

generics

map

The problem: I've a Function Object interface defined in a class:

    public static interface FunctionObject<T>  {
        void process(T object);
    }

I need it generic because I'd like to use T methods in the process implementations.
Then, in other generic class, I've a Map where I have classes as keys and function objects as values:

    Map<Class<T>, FunctionObject<T>> map;

But I also want the map to accept subtype classes and function objects of supertypes OF THE KEY TYPE, so I did this:

    Map<Class<? extends T>, FunctionObject<? super T>> map; //not what I need

The basic idea is to be able to use the map as follows:

    //if T were Number, this should be legal
    map.put(Class<Integer>, new FunctionObject<Integer>(){...});
    map.put(Class<Float>, new FunctionObject<Number>(){...});
    map.put(Class<Double>, new FunctionObject<Object>(){...});

As I want to enforce the FunctionObject has the type of the class key or a supertype, what I really would like to define is this:

    Map<Class<E extends T>, FunctionObject<? super E>>> map;

How can I achieve the desired effect? Is a typesafe heterogenous container the only option? What would the Map generic types look like to allow populating it from a reference?

like image 367
Mister Smith Avatar asked Jan 13 '12 09:01

Mister Smith


1 Answers

Parametrized container, seems to work just fine:

public class MyMap<T>
{
    interface FunctionObject<X> {}

    private Map<Class<? extends T>, FunctionObject<Object>> map = new HashMap<>();

    @SuppressWarnings("unchecked")
    public <E extends T> void put(Class<E> c, FunctionObject<? super E> f)
    {
        map.put(c, (FunctionObject<Object>) f);
    }

    public <E extends T> FunctionObject<Object> get(Class<E> c)
    {
        return map.get(c);
    }

    public static void Main(String[] args)
    {
        MyMap<Number> map = new MyMap<>();

        map.put(Integer.class, new FunctionObject<Integer>() {});
        map.put(Float.class, new FunctionObject<Number>() {});
        map.put(Double.class, new FunctionObject<Object>() {});
    }
}

Edited to comply to the question. Sadly there is no way to avoid the downcasting to object.

Edit added get().

like image 198
Viruzzo Avatar answered Nov 11 '22 06:11

Viruzzo