Saturday, 29 May 2010

Type-safe arbitary properties in Java

Yesterday a colleague approached me regarding the best method for associating arbitrary properties with an object in Java. The object currently has a Map<String,String> for which it exposes public setProperty and getProperty methods, but he wanted to be able to associate properties other than Strings, without having to serialize them.

I have seen Objects using a Map<String,Object> to store properties, but this leads to code becoming littered with casts, with the associated risks of ClassCastExceptions appearing at runtime.

My suggested solution was to use generics to hide the property map behind a type-safe facade.

First we define a generically typed Key:
public class Key<T> {

private final String name;

public Key(String name) {
this.name = name;
}

@Override
public int hashCode() {
return 37 * (name != null ? name.hashCode() : 0);
}

@Override
public boolean equals(Object obj) {
return this == obj;
}

@Override
public String toString() {
return "Key{" + name + "}";
}

}

When we use this as the key for our property map, the Java compiler can infer the Object type from the key, preventing us from associating a mistyped Object with a key, and correctly casting the Object back when we get it:
public class MyObject {

private Map<Key,Object> properties = new HashMap<Key, Object>();

public <T> void setProperty(Key key, T value) {
properties.put(key, value);
}

public <T> T getProperty(Key<T> key) {
return (T) properties.get(key);
}

}

The types can be determined at compile time, so there is no need for casts:
  MyObject obj = new MyObject();

Key kstr = new Key("foo");
Key kint = new Key("bar");

obj.setProperty(kstr, "string");
obj.setProperty(kint, 123);
String s = obj.getProperty(kstr);
Integer i = obj.getProperty(kint);

Errors will also be detected by the compiler:
  // Try to set a String value to an Integer-typed property
obj.setProperty(kint, "xyz");

// Try to cast the value of an Integer-typed property to a String
String x = (String) obj.getProperty(kint);

2 comments:

IDontHaveABlogIJustWantedToComment said...

Awesome! But... I doesn't prevent me from doing this:
MyObject obj = new MyObject();
Key<String> kstr = new Key<String>("foo");
Key<Integer> kint = new Key<Integer>("foo");
obj.setProperty(kstr, "xyz");
obj.getProperty(kint);

summ3r said...

A good tool to translate Java properties files is POEditor, in case you need one.

Post a Comment