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 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 extends MethodFinder, Function {
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 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?