Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Mapping Mixed Types

Tags:

java

hashmap

I am looking for a way to create a HashMap that maps to mixed types.

For example, the following would work in Python (note that a Python dict is similar to a Java HashMap):

d = dict()
d["str"] = "This is a string"
d["flt"] = 1.23
d["int"] = 5

and when accessing d[x], the appropriate type would be returned (string, float, int in this example).

In Java, I could go with a HashMap<String, Object>, but I don't really have a way to immediately tell what type each Object is.

So instead, I'm currently trying to do is create a MixedType class that holds an object, but also its original type information, so that I can downcast it later. For example:

public class MixedMap
    public static class MixedType
    {
        public Class<?> cls;
        public Object val;

        public MixedType(Class<?> c, Object v)
        {
            this.cls = c;
            this.val = v;
        }
    }

    public static void main(String args[])
    {   
        MixedType m1 = new MixedType(String.class, "This is a String");
        System.out.println((m1.cls)m1.val);
    }
}

Note that this is what I'm trying to do :-) as it's currently not compiling complaining that m1 cannot be resolved to a type.

So my question is, how can I fix this? Other ways to achieve the same goal would be welcomed, but please don't answer telling me that this limits the type checking ability of the compiler -- I know that and am OK with it.

Thanks in advance.

like image 517
jedwards Avatar asked Apr 10 '12 00:04

jedwards


2 Answers

The proper thing to do is m1.cls.cast(m1.val), but you almost certainly won't gain anything as compared to a plain Map<String, Object>. A simpler approach that would get you exactly as much type safety and exactly the same information -- which is to say, not much -- would just be to use the getClass() method on the Object values in your map.

None of this will accomplish your goal of being able to say String value = map.get("str") -- nothing short of an outright cast would do that. You could do

Object value = map.get("str");
if (value instanceof String) {
  // do stringy things
}

or something along those lines.

If you know the types of the values at their use points at compile time, then by far the best solution is just to go ahead and do the casts at the usage points. The "ideal" way to do configuration like this in general is not to use a Map, but to build a customized Configuration class that knows its fields and their types at compile time, but if you want to use a Map, then just doing the casts directly is probably your best bet.

like image 116
Louis Wasserman Avatar answered Nov 11 '22 04:11

Louis Wasserman


I've implemented something similar to this before. You can use generics to assist. What you want is a MixedKey, that you can use as a key into a MixedMap. Something along these lines:

public class MixedKey<T> {
    public final Class<T> cls;
    public final String key;

    public MixedKey(T cls, String key) {
        this.key = key;
        this.cls = cls;
    }

    // also implement equals and hashcode
}

.

public class MixedMap extends HashMap<MixedKey<?>,Object> {
    public <T> T putMixed(MixedKey<T> key, T value) {
        return key.cls.cast(put(key, value));
    }

    public <T> T getMixed(MixedKey<T> key) {
        return key.cls.cast(get(key));
    }
}

The nice thing about doing something like this is it will give you a compile-time error if you use the wrong type by mistake, something you don't get with direct casting:

MixedKey<Integer> keyForIntProperty
    = new MixedKey<Integer>(Integer.class, "keyForIntProperty");

// ...

String str = mixedMap.getMixed(keyForIntProperty); // compile-time error

vs.

String str = (String)map.get("keyForIntProperty"); // run-time error only
like image 44
Kevin K Avatar answered Nov 11 '22 04:11

Kevin K