There is a killer feature in Java 8, and it is not Lambdas.
The Bean API has always been one of the most frustrating parts of Java Enterprise coding. The need to to pattern matching on object names has always seemed the worst option. But it necessary due to the fact that there was no other way to enumerate the properties of an object and still ensure that the object stayed valid.
When Inversion of Control frameworks first became generally known, I tried to use Constructor injection. This seems the preferred method, as it allows you to make your objects immutable, and to make sure that, once your constructor returns, you have a valid object. Property injection violates both of these principals. But Property inject was the norm. With property injection, you could take a text file or any other key-value pair representation, and use it to configure an object. But Java does not maintain the names of function parameters. With Constructor injection, you had no way of matching the key to the Parameters of the constructor.
The Java 8 spec has added support for parameter names. With that addition, a programmer can instantiate an object from a dictionary with only run-time knowledge.
Here is a simple Data Transfer Object that represent a subset of data you might get back from an LDAP query. It is immutable.
package com.younglogic.dictionary; import java.lang.reflect.Field; import java.lang.StringBuffer; public class User { public final int uid; public final int gid; public final String loginShell; public final String jobTitle; public final String name; public User(int uid, int gid, String loginShell, String jobTitle, String name) { super(); this.uid = uid; this.gid = gid; this.loginShell = loginShell; this.jobTitle = jobTitle; this.name = name; } public String toString(){ StringBuffer out = new StringBuffer(); for (Field prop: this.getClass().getDeclaredFields()){ try{ out.append (String.format("%s = %s \n", prop.getName(), prop.get(this))); }catch (IllegalAccessException e){ } } return out.toString(); } } |
Not that there are multiple parameters of the same type. To know whether a given parameter is uid or gid in Java 7 and earlier, you had to know the order. But with parameter names, we can query that information from the object.
The following code will instantiate it from a Dictionary (Map from String to String).
package com.younglogic.dictionary; import java.util.Map; import java.util.HashMap; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Parameter; import java.lang.reflect.Type; public class Builder { public static Object parseToPrimitive(String value, Type type){ if (type == Integer.TYPE) { return Integer.parseInt(value); }else if (type == Long.TYPE) { return Long.parseLong(value); } /* And so on */ return null; } public static void main(String[] args){ Map< String, String > props = new HashMap< String, String >(); props.put("uid","1"); props.put("gid","2"); props.put("loginShell","/bin/bash"); props.put("jobTitle", "codemonkey"); props.put("name", "Adam"); Class c = User.class; Constructor constructor = c.getConstructors()[0]; Parameter[] params = constructor.getParameters(); Object[] constructorParams = new Object[params.length]; int i = 0; try{ for (Parameter param: params){ String val = props.get(param.getName()); if (param.getType() == String.class){ constructorParams[i] = val; }else if (param.getType().isPrimitive()){ constructorParams[i] = parseToPrimitive (val, param.getType()); }else{ constructorParams[i] =param.getType().getConstructor (String.class).newInstance(val); } i++; } User user = (User)constructor.newInstance(constructorParams); System.out.println (user); } catch(Exception e){ System.out.println (e); } } } |
Note the call to constructor.getParameters(); and then param.getName(). These both have vestiges of the Bean API in their naming. Contrast that with an array, where the length property is public and immutable.
Unfortunately, the switch to make this work is not on by default in Java 8. If you compile with the default options, the names are arg0 through arg4. To tel javac to “remember” the names, you need to compile with the -parameter switch.
#!/usr/bin/bash -x JAVAC=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/javac JAVA=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/java $JAVAC -parameters src/com/younglogic/dictionary/*.java $JAVA -cp src com.younglogic.dictionary.Builder |
The Above code then produces:
$ ./compile.sh + JAVAC=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/javac + JAVA=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/java + /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/javac -parameters src/com/younglogic/dictionary/Builder.java src/com/younglogic/dictionary/User.java + /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.0.x86_64/bin/java -cp src com.younglogic.dictionary.Builder uid=1 gid=2 loginShell=/bin/bash jobTitle=codemonkey name=Adam |
This should become the default way of working between text documents, especially JSON and YAML, and Java classes.
I would say, IoC was never designed to create immutable beans. But if you want to do it, save yourself a bit of time and use Factory pattern.
If programmers are not able to remember order of constructor arguments what makes you think they will remember their names? For that we have JavaDoc. 😉
Factory pattern solves a different problem. I would use the two approaches together: the consumer should use the factory (really a lazy load proxy) to fetch the instance, and not know about its implementation.
But with named parameters, an IofC framework now has a way for the class to document “Here is what I need” with a means to distinguish between each of the parameters, even if they are the same type.