Jim
Jim

Reputation: 19582

Dictionary-like data structure. Is this a good practice?

I need a data structure to store different type of objects.E.g. String, Boolean and other classes.
Is using a Map<String, Object> where using the key you get the according object which assumes that you know how to cast it a good practice?
Is there a better solution?

Upvotes: 4

Views: 573

Answers (2)

Lorand Bendig
Lorand Bendig

Reputation: 10650

A typesafe heterogeneous container can be used for this purpose:

import java.util.HashMap;
import java.util.Map;

public class Container {

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

    public <T> void putElement(Class<T> type, T instance) {
        if (type == null) {
            throw new NullPointerException("Type is null");
        }
        //container.put(type, instance); // 'v1'
        container.put(type, type.cast(instance)); // 'v2' runtime type safety!
    }

    public <T> T getElement(Class<T> type) {
        return type.cast(container.get(type));
    }

    public static void main(String[] args) {

        Container myCont = new Container();
        myCont.putElement(String.class, "aaa");
        myCont.putElement(Boolean.class, true);
        myCont.putElement(String[].class, new String[] {"one", "two"});

        System.out.println(myCont.getElement(String.class));
        System.out.println(myCont.getElement(String[].class)[1]);

    }

}

Limitation: this container in its form is capable only to store one instance/object type.

In putElement() you can achieve runtime type safety by using a dynamic cast. This will hoewever add an extra overhead.

E.g: Try to pass a raw class object to the container. Note where the exception occurs:

Class raw = Class.forName("MyClass");
myCont.putElement(raw, "aaa"); //ClassCastException if using 'v2'
System.out.println(myCont.getElement(raw)); //ClassCastException if using 'v1'

Upvotes: 2

Ivan Koblik
Ivan Koblik

Reputation: 4315

That's a perfect use case for a PropretyHolder I wrote a while ago. You can read in length about it on my blog. I developed it with immutability in mind, feel free to adapt it to your needs.

In general I'd say if you want to profit from type safety in Java you need to know your keys. What I mean by that - it will be hardly possible to develop type safe solution where keys come from external source.


Here's a special key that knows type of its value (it's not complete please download the source for complete version):

public class PropertyKey<T> {
    private final Class<T> clazz;
    private final String name;

    public PropertyKey(Class<T> valueType, String name) {
        this.clazz = valueType;
        this.name = name;
    }

    public boolean checkType(Object value) {
        if (null == value) {
            return true;
        }
        return this.clazz.isAssignableFrom(value.getClass());
    }

    ... rest of the class

}

Then you develop a data structure that utilizes it:

public class PropertyHolder {

    private final ImmutableMap<PropertyKey<?>, ?> storage;

    /**
     * Returns value for the key of the type extending-the-one-declared-in-the {@link PropertyKey}.
     * 
     * @param key {@link PropertyKey} instance.
     * @return Value of the type declared in the key.
     */
    @SuppressWarnings("unchecked")
    public <T extends Serializable> T get(PropertyKey<T> key) {
        return (T) storage.get(key);
    }

    /**
     * Adds key/value pair to the state and returns new 
     * {@link PropertyHolder} with this state.
     * 
     * @param key {@link PropertyKey} instance.
     * @param value Value of type specified in {@link PropertyKey}.
     * @return New {@link PropertyHolder} with updated state.
     */
    public <T> PropertyHolder put(PropertyKey<T> key, T value) {
        Preconditions.checkNotNull(key, "PropertyKey cannot be null");
        Preconditions.checkNotNull(value, "Value for key %s is null", 
                key);
        Preconditions.checkArgument(key.checkType(value), 
                "Property \"%s\" was given " 
                + "value of a wrong type \"%s\"", key, value);
        // Creates ImmutableMap.Builder with new key/value pair.
        return new PropertyHolder(filterOutKey(key)
                .put(key, value).build());
    }

    /**
     * Returns {@link Builder} with all the elements from the state except for the given ket.
     * 
     * @param key The key to remove.
     * @return {@link Builder} for further processing.
     */
    private <T> Builder<PropertyKey<? extends Serializable>, Serializable> filterOutKey(PropertyKey<T> key) {
        Builder<PropertyKey<? extends Serializable>, Serializable> builder = ImmutableMap
                .<PropertyKey<? extends Serializable>, Serializable> builder();
        for (Entry<PropertyKey<? extends Serializable>, Serializable> entry : this.storage.entrySet()) {
            if (!entry.getKey().equals(key)) {
                builder.put(entry);
            }
        }
        return builder;
    }

    ... rest of the class

}

I omit here a lot of unnecessary details please let me know if something is not clear.

Upvotes: 5

Related Questions