Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the enclosing class of a Java lambda expression

Tags:

java

lambda

I have a method that takes a functional parameter, e.g. a Runnable. As it's a library method, I want it to use a logger derived from the functional parameter. Calling getClass on the functional parameter works fine for normal classes, and I can getEnclosingClass for nested or anonymous classes; but if it's a lambda expression, it returns some obscure name containing $$Lambda$, which I could strip off manually like this:

Class<?> type = runnable.getClass();
String canonical = type.getCanonicalName();
int lambdaOffset = canonical.indexOf("$$Lambda$");
if (lambdaOffset > 0) {
    try {
        type = Class.forName(canonical.substring(0, lambdaOffset));
    } catch (ClassNotFoundException e) {
        // strange, but we can stick to the type we already have
    }
}

As you can see, that's not very elegant and probably not portable. I've tried getEnclosingClass, getEnclosingMethod, and getEnclosingConstructor, but they all return null.

Any ideas?

like image 329
rü- Avatar asked Jan 04 '16 10:01

rü-


2 Answers

As already mentioned by Tassos Bassoukos it is by design.

The bytecode for the Lambda (class) is generated at runtime. So what you get is the actual name of the class. And the name is generated as target class name + "$$Lambda$" + a counter.

Find a small snippet for demonstration.

package sub.optimal;
import static java.lang.System.out;

public class EnclosingClass {

    static class InnerRunnable implements Runnable {

        @Override
        public void run() {
            out.println("--- inner class");
        }
    }

    public static void main(String... args) {
        showIdentity(() -> System.out.println("--- lambda 1"));
        showIdentity(() -> System.out.println("--- lambda 2"));
        showIdentity(new InnerRunnable());
        showIdentity(new Runnable() {
            @Override
            public void run() {
                out.println("--- anonymous class");
            }
        });
    }

    private static void showIdentity(Runnable runnable) {
        runnable.run();
        Class<? extends Runnable> clazz = runnable.getClass();
        out.printf("class name     : %s%n", clazz.getName());
        out.printf("class hashcode : %s%n", clazz.hashCode());
        out.printf("canonical name : %s%n", clazz.getCanonicalName());
        out.printf("enclosing class: %s%n", clazz.getEnclosingClass());
        out.println();
    }
}

output

--- lambda 1
class name     : sub.optimal.EnclosingClass$$Lambda$1/2147972
class hashcode : 2147972
canonical name : sub.optimal.EnclosingClass$$Lambda$1/2147972
enclosing class: null

--- lambda 2
class name     : sub.optimal.EnclosingClass$$Lambda$2/10376386
class hashcode : 10376386
canonical name : sub.optimal.EnclosingClass$$Lambda$2/10376386
enclosing class: null

--- inner class
class name     : sub.optimal.EnclosingClass$InnerRunnable
class hashcode : 28014437
canonical name : sub.optimal.EnclosingClass.InnerRunnable
enclosing class: class sub.optimal.EnclosingClass

--- anonymous class
class name     : sub.optimal.EnclosingClass$1
class hashcode : 19451386
canonical name : null
enclosing class: class sub.optimal.EnclosingClass
like image 186
SubOptimal Avatar answered Oct 12 '22 07:10

SubOptimal


I found a cool solution by benjiweber. It boils down to serializing the lamda to a java.lang.invoke.SerializedLambda and then get its declaring class:

private static final int COUNT = 1_000_000;
private static boolean first = true;

public static void main(String[] args) {
    long t = System.currentTimeMillis();
    for (int i = 0; i < COUNT; i++) {
        showIdentity(() -> {
        });
    }
    String time = NumberFormat.getNumberInstance().format((double) (System.currentTimeMillis() - t) / COUNT);
    System.out.println("time per call: " + time + "ms");
}

public interface MethodAwareRunnable extends Runnable, Serializable {}

private static void showIdentity(MethodAwareRunnable consumer) {
    consumer.run();
    String name = name(consumer);
    if (first) {
        first = false;
        Class<?> clazz = consumer.getClass();
        System.out.printf("class name     : %s%n", clazz.getName());
        System.out.printf("class hashcode : %s%n", clazz.hashCode());
        System.out.printf("canonical name : %s%n", clazz.getCanonicalName());
        System.out.printf("enclosing class: %s%n", clazz.getEnclosingClass());
        System.out.printf("lambda name    : %s%n", name);
    }
}

private static String name(Object consumer) {
    return method(consumer).getDeclaringClass().getName();
}

private static SerializedLambda serialized(Object lambda) {
    try {
        Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace");
        writeMethod.setAccessible(true);
        return (SerializedLambda) writeMethod.invoke(lambda);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private static Class<?> getContainingClass(SerializedLambda lambda) {
    try {
        String className = lambda.getImplClass().replaceAll("/", ".");
        return Class.forName(className);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private static Method method(Object lambda) {
    SerializedLambda serialized = serialized(lambda);
    Class<?> containingClass = getContainingClass(serialized);
    return Arrays.stream(containingClass.getDeclaredMethods())
                 .filter(method -> Objects.equals(method.getName(), serialized.getImplMethodName()))
                 .findFirst()
                 .orElseThrow(RuntimeException::new);
}

This is a lot of code, but the overhead is about 0.003 ms on my machine, which is okay for most use cases.

And you can do other cool stuff like:

Map<String, String> hash = hash(
    hello -> "world",
    bob -> "was here"
);
like image 35
rü- Avatar answered Oct 12 '22 06:10

rü-