Search code examples
javagenericstype-safety

Java Map with variable generics as values


So here's a slightly tricky question (for me).

I have a generic object. Call it MyObject. This object has a method which returns something of the type T:

public class MyObject<T>
{
    private T _t;

    public MyObject(T t)
    {
        _t = t;
    }

    //...

    public T get()
    {
        return _t;
    }
}

(Obviously my "MyObject" does a bit more but that's the gist).

Now, I want to have a map of this type:

Map<String, MyObject<?>> m = new HashMap<>();

I want to be able to fetch maps using some predefined string name, and these maps can be of any MyObject. For example, I could call:

m.put("map_1", new MyObject<String>("String"));
m.put("map_2", new MyObject<Integer>(new Integer(3));
m.put("map_3", new MyObject<Long>(new Long(5));

etc.

But - and here's the tricky part - I want the map to "remember" the parameterized type of MyObject when I fetch some value from the map. Using

m.get("map_1");

would return a

MyObject<Object> 

type, since the map was defined as containing

MyObject<?> 

values. Thus:

m.get("map_1").get() // <-- This is an Object, not a String! 

What modification (if any) is possible, in order to be able to get the correct - full - information regarding the MyObject fetched object, such that invoking the last line (m.get("map_1")) would return a

MyObject<String>

Thanks :)

Amir.


Solution

  • Typesafe Heterogeneous Containers from Joshua Bloch's Effective Java might work here. Basically you add a Class object to represent the type.

    public class MyObject<T>
    {
        private T _t;
        private Class<T> type;
    
        public MyObject( Class<T> type, T t)
        {
            _t = t;
            this.type = type;
        }
    
        //...
    
        public T get()
        {
            return _t;
        }
    
        public Class<T> getType() { return type; }
    }
    

    Then you could do something like this:

    public <T> T get( Map<String, MyObject<?>> map, String key, Class<T> type ) {
       return type.cast( m.get( key ).get() );
    }
    

    Which is safe and will compile, but will throw a runtime error if you get the type wrong.

    (Note I didn't actually compile that, so I might have syntax errors floating around. But most folks don't know how to use Class to cast objects.)