1

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.

3
  • 2
    There's nothing type safe you can do here. Commented Apr 19, 2017 at 23:47
  • 2
    There's a design example, Heterogeneous Type I think, in Joshua Bloch's *Effective Java" that might meet your requirements. Commented Apr 19, 2017 at 23:47
  • What about Reflection? Commented Apr 19, 2017 at 23:56

3 Answers 3

6

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

Sign up to request clarification or add additional context in comments.

5 Comments

Hey, thank you - this looks very close to what I needed. A question though - as I understand, the "Class<T>" argument is like a "token" passing the type information, such that the generic method get <T> will know that we want to get a "T". So I could then simply pass a T object, no? It could be public <T> T get(Map<String, MyObject<?>> map, String key, T t){...}
I dont understand why we would store the Class as a separate field when each object contains its Class already.
@amirkr I think the distinction occurs when you want to pass an object that's part of a hierarchy. For example, you actually pass in a JLabel, but you want its type to be JComponent.
@JoseMartinez The problem is the class type isn't really there. It's erased. It works in your example, but if you change your program at all, for example if the generic class is actually an interface not the class, your example won't work any more. Finding a parameterized type through reflection is actually really tricky and difficult.
That doesn’t explain the value of that type field in MyObject at all—it isn’t used anywhere. The runtime check is performed using parameter type of the get(Map<String,MyObject<?>>,String,Class<T>) method, which would work exactly the same without the type field in MyObject. There might be scenarios where that pattern is useful, but here, it’s just an used additional field…
0

You can get the class.

Class c = m.get("map_1").get().getClass();
if (String.class.equals(c)) {
    System.out.println("its a String");
}

Here is a full test.

public class GenericsTest {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        Map<String, MyObject<?>> map = new HashMap<>();
        MyObject<String> obj = new MyObject<>("hello");

        map.put("greeting", obj);

        Class c = map.get("greeting").get().getClass();
        if (String.class.equals(c)) {
            System.out.println("its a String");
        }

    }

    static class MyObject<T> {

        T t;

        public MyObject(T t) {
            this.t = t;
        }

        T get() {
            return t;
        }

    }

}

2 Comments

Hey, thanks! I don't only need to get the class, but to act on it - so if I got a String, the code "will know" its a string and be able to act on its methods. A response that might be helpful in that particular context was discussed in another comment here (I hope). Thanks for the effort, the "Class" object mechanism is still a bit vague for me and your code helped :)
I do not think you need to store the Class as a separate field when each Object already contains its Class.
0

The type system only knows about types, not objects, and therefore can not distinguish "key1" from "key2", because both are of type String.

If keys have different types, the easiest way is to encapsulate a weakly typed map, and use reflective casts to prove to the compiler the types are correct:

class Favorites {

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

    <V> V get(Class<V> clazz) {
        return clazz.cast(map.get(clazz));
    }

    <V> void put(Class<V> clazz, V value) {
        map.put(clazz, value);
    }
}

Favorites favs = new Favorites();
favs.put(String.class, "hello");
favs.put(Integer.class, 42);
favs.get(String.class).charAt(1); 

1 Comment

Hey, thank you. The keys have the same type (in my example - they are all strings), but the values have different types. What I wanted was to be able to infer the type directly when invoking the "get" method with some key - so, it I got a String the code "will know" (and allow me to use the string's methods), etc. The usage of Class is interesting though, as was also mentioned in another comment. Thanks for the input (pun intended) ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.