Java 8 introduced a compiler flag -parameters, which makes method parameter names available at runtime with reflection. Up to now, this has not worked with lambda parameter names. However, Java 8u60 now has a fix for this bug back-ported which makes it possible.
Some uses that spring to mind (and work as of recent 8u60ea build) are doing things like hash literals
Map<String, String> hash = hash( hello -> "world", bob -> bob, bill -> "was here" ); assertEquals("world", hash.get("hello")); assertEquals("bob", hash.get("bob")); assertEquals("was here", hash.get("bill")); |
Or just another tool for creating DSLs in Java itself. For example, look how close to puppet’s DSL we can get now.
static class Apache extends PuppetClass {{ file( name -> "/etc/httpd/httpd.conf", source -> template("httpd.tpl"), owner -> root, group -> root ); cron ( name -> "logrotate", command -> "/usr/sbin/logrotate", user -> root, hour -> "2", minute -> "*/10" ); }} |
Doing
System.out.println(new Apache().toString()); |
Would print
class Apache { file{ name => "/etc/httpd/httpd.conf", source => template(httpd.tpl), owner => root, group => root, } cron{ name => "logrotate", command => "/usr/sbin/logrotate", user => root, hour => "2", minute => "*/10", } } |
The code examples for the hash literal example and the puppet example are on Github.
They work by making use of a functional interface that extends Serializable. This makes it possible to get access to a SerializedLambda instance, which then gives us access to the synthetic method generated for the lambda. We can then use the standard reflection API to get the parameter names.
Here’s a utility MethodFinder interface that we can extend our functional interfaces from that makes it easier.
public interface MethodFinder extends Serializable { default SerializedLambda serialized() { try { Method replaceMethod = getClass().getDeclaredMethod("writeReplace"); replaceMethod.setAccessible(true); return (SerializedLambda) replaceMethod.invoke(this); } catch (Exception e) { throw new RuntimeException(e); } } default Class<?> getContainingClass() { try { String className = serialized().getImplClass().replaceAll("/", "."); return Class.forName(className); } catch (Exception e) { throw new RuntimeException(e); } } default Method method() { SerializedLambda lambda = serialized(); Class<?> containingClass = getContainingClass(); return asList(containingClass.getDeclaredMethods()) .stream() .filter(method -> Objects.equals(method.getName(), lambda.getImplMethodName())) .findFirst() .orElseThrow(UnableToGuessMethodException::new); } default Parameter parameter(int n) { return method().getParameters()[n]; } class UnableToGuessMethodException extends RuntimeException {} } |
Given the above, we can create a NamedValue type that allows a lambda to represent a mapping from a String to a value.
public interface NamedValue<T> extends MethodFinder, Function<String, T> { default String name() { checkParametersEnabled(); return parameter(0).getName(); } default void checkParametersEnabled() { if (Objects.equals("arg0", parameter(0).getName())) { throw new IllegalStateException("You need to compile with javac -parameters for parameter reflection to work; You also need java 8u60 or newer to use it with lambdas"); } } default T value() { return apply(name()); } } |
Then we can simply ask our lambda for the name and value.
NamedValue<Integer> pair = five -> 5; assertEquals("five", pair.name()); assertEquals(5, pair.value()); |
See also HTML in Java for another example use of this.
I imagine there are more uses too, any ideas?