Arrows of Outrageous Fortune

At Java One, I overheard someone mentioning an outrageous trick to abuse the arrow -> of the lambda syntax. Of course I had to investigate. Here are the gory details...and please, kids, don't try this at home.

Unfortunately, Java doesn't have a convenient syntax for initializing lists and maps. In Scala, you can write

val scores = Map("Harry" -> 1, "Fred" -> 2)

but in Java, it is

Map<String, Integer> scores = new HashMap<>();
scores.put("Harry", 1);
scores.put("Fred", 2);

Years ago, I discovered with morbid fascination that one can use anonymous subclasses and an obscure initializer syntax to make this a bit less painful:

Map<String, Integer> scores = new HashMap<>() {{
   put("Harry", 1);
   put("Fred", 2);
}};

This is called double brace initialization, and it is in general not a good idea. A new class is created, and the instance may or may not test correctly with equals, depending on how the equals method is implemented.

Now that we have actual arrows in Java, can we abuse them for defining maps? Our friends in the world of C# are doing it.

The idea is to use a lambda expression such as

Harry -> 1

As with any lambda expression, there is just one thing one can do with it: turn it into an instance of a functional interface. Like this:

Function<String, Integer> f = Harry -> 1;

Now f is a function that yields 1 for any string input.

Given f, it is easy enough to recover the value: just call f.apply(""). But what about Harry? Note that it's not a string. It is the name of the parameter.

As it turns out, since JDK 8, one can compile a Java program with

javac -parameters Program.java

Then the names of the parameters are included in class files and can be retrieved through reflection.

As an aside, this is potentially useful because it can reduce annotation boilerplate. Consider a typical JAX-RS method

Person getEmployee(@PathParam("dept") Long dept, @QueryParam("id") Long id)

In almost all cases, the parameter names are the same as the annotation arguments, or they can be made to be the same. If the annotation processor could read the parameter names, then one could simply write

Person getEmployee(@PathParam Long dept, @QueryParam Long id)

Let’s hope annotation writers will enthusiastically embrace this mechanism, so there will be momentum to drop that compiler flag in the future.

Back to our little puzzle. We could get to the parameter name if we had a java.lang.reflect.Method instance for the method of the lambda expression. To do that, we need to know a bit about how lambda expressions are compiled. The Java compiler generates a static method such as

private static java.lang.Integer lambda$main$7796d112$1(java.lang.String)

in the class file of the class containing our lambda expression. That method is ultimately called when the lambda expression is invoked. And it has the desired parameter name, as you can verify by calling

javap -c -p -v Program

Here is the relevant part of the output:

...
 private static java.lang.Integer lambda$main$7796d112$1(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/Integer;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_2
         1: invokestatic  #37                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: areturn
      LineNumberTable:
        line 49: 0
    MethodParameters:
      Name                           Flags
      Harry                          synthetic
...

Of course, if there are multiple lambda expressions, one needs to find the correct method if one wants to recover the parameter name. Benji Weber's blog shows how to do that.

Lambda expressions can be serialized into instances of the class java.lang.invoke.SerializedLambda, from which one can retrieve the name of the class containing the implementation method, as well as its name.

You get the names of the class and method as

String className = serializedLambda.getImplClass().replaceAll("/", ".");
   // in our example, "Program"
String methodName = serializedLambda.getImplMethodName()
  // in our example, "lambda$main$7796d112$1"

Now we just need to find the matching Method instance and get its parameter name:

Class<?> containingClass = Class.forName(className);
Method m = containingClass.getDeclaredMethod(methodName, String.class);
return m.getParameters()[0].getName();

But how do we get the SerializedLambda from the functional interface instance? You need to call the writeReplace method on the instance. That's a private method, so you need to use reflection:

Method replaceMethod = f.getClass().getDeclaredMethod("writeReplace");
replaceMethod.setAccessible(true);
SerializedLambda serializedLambda
   = (SerializedLambda) replaceMethod.invoke(this);

Here is a complete program with all the pieces. The plumbing is placed into a functional interface Arrow<T>. You can then call

Arrow<Integer> f = Harry -> 1;
System.out.println(f.getName() + " " + f.getValue()); 

More usefully, you can define a method for constructing maps whose keys are strings:

@SafeVarargs static <T> Map<String, T> map(Arrow<T> ... arrows)
{
   Map<String, T> result = new HashMap<>();
   for (Arrow<T> arrow : arrows) result.put(arrow.getName(),
      arrow.getValue());
   return result;
}

To make a map, call

Map<String, Integer> m = Arrow.map(Harry -> 1, Fred -> 2);

Of course, that only works if the map keys are valid identifiers.

Benji Weber has another example for HTML builders.

It's great fun that you can do that, but it also feels quite repulsive. It just doesn't seem to be in the spirit of Java to bend the rules like this. Don't try this at home!