benjiPosted under Java.

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?

Leave a Reply

  • (will not be published)