Data objects for the lazy: A function to auto implement a Java interface
One of the things I like about a language like Scala is how little you have to type to get a data class with getters and setters for each field. Now in Java this is a bit more painful:
class Person {
private String name;
private String surname;
private int age;
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setSurname(String surname) {
this.surname = surname;
}
public void setAge(int age) {
this.age = age;
}
}
All I wanted to say here is that I want a container that stores read/write values for the name, surname and age of a person. Why do I have to type so much? Hmmm, perhaps I can say it a like so:
interface Person {
public String getName();
public void setName(String name);
public String getSurname();
public void setSurname(String surname);
public int getAge();
public void setAge(int age);
}
Brilliant, except that I can’t store anything in it. Its like having an bright idea with no implementation. But I’ve stated what I want did I not, why can’t the compiler fill in the details for me? Is there not a magic wand method that I can pass this interface to and get an implementation back or can’t I have someone type the details out for me? But even if someone over eager typed it out for me then I will have the clutter of the implementation which is just saying again what I already asked the compiler for.
Hmm I just want a container. Perhaps I should just use a map?
Map<String, Object> person = new HashMap<String, Object>();
person.put("name", "Bob");
person.put("surname","Dylan");
person.put("age", 23);
Hey if this was Javascript I could use a map like an object:
person.name = "Bob";
person.surname = "Dylan";
person.age = 23;
This makes sense because isn’t an object doing the same thing as a map conceptually i.e. mapping a string (the field name) to a value? But unfortunately this is not how Java sees things. Hey but what if we blend the concept of a HashMap and a Java interface in a Dynamic Proxy blender to give us a magic wand? Hmm:
class LazyProgrammerToolBox {
/**
* Creates an object that implements the specified interface
* and whose data values associated with getter/setter
* methods are stored in a map.
*
* @param <T>
* The interface type
* @param interfaceClass
* The interface class
* @return A new instance of type T
*/
public static <T> T getMapBackedImplementation(Class<T> interfaceClass) {
int c = interfaceClass.getDeclaredMethods().length;
return getMapBackedImplementation(interfaceClass,
new HashMap<String, Object>(c));
}
/**
* Creates an object that implements the specified interface
* and whose data values associated with getter/setter
* methods are stored in the specified map.
*
* @param <T>
* The interface type
* @param interfaceClass
* The interface class
* @param container
* The map where the properties will be stored.
* @return A new instance of type T
*/
public static <T> T getMapBackedImplementation(Class<T> interfaceClass,
final Map<String, Object> container) {
InvocationHandler handler;
if (container == null) {
// Create a black-hole handler...
handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return method.getReturnType().cast(null);
}
};
} else {
handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getReturnType() != void.class) {
String s = method.getName();
if (s.equals("toString"))
return container.toString();
boolean g = s.startsWith("get");
boolean i = !g || s.startsWith("is");
s = g ? s.replaceFirst("get", "")
: i ? s.replaceFirst("is", "")
: s;
if (g || i) {
s = Character.toLowerCase(
s.charAt(0)) + s.substring(1);
}
return container.get(s);
} else {
if (args.length > 0) {
String s = method.getName();
boolean set = s.startsWith("set");
if (set) {
s = s.replaceFirst("set", "");
s = Character.toLowerCase(
s.charAt(0)) + s.substring(1);
}
container.put(s, args[0]);
}
return null;
}
}
};
}
return interfaceClass.cast(Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[] { interfaceClass },
handler));
}
}
Lets see how do I use this wand:
Person p = LazyProgrammerToolBox.getMapBackedImplementation(Person.class);
p.setName("Bob");
p.setSurname("Dylan");
p.setAge(23);
Aah, thats better 🙂