简体   繁体   English

获取Java lambda表达式的封闭类

[英]Get the enclosing class of a Java lambda expression

I have a method that takes a functional parameter, eg a Runnable . 我有一个采用功能参数的方法,例如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; 在函数参数上调用getClass适用于普通类,并且我可以为嵌套或匿名类获取getEnclosingClass ; but if it's a lambda expression, it returns some obscure name containing $$Lambda$ , which I could strip off manually like this: 但如果它是一个lambda表达式,它会返回一个含有$$Lambda$模糊名称,我可以像这样手动剥离:

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 . 我尝试过getEnclosingClassgetEnclosingMethodgetEnclosingConstructor ,但它们都返回null

Any ideas? 有任何想法吗?

As already mentioned by Tassos Bassoukos it is by design. 正如Tassos Bassoukos已经提到的,它是设计的。

The bytecode for the Lambda (class) is generated at runtime. Lambda(类)的字节码是在运行时生成的。 So what you get is the actual name of the class. 所以你得到的是班级的实际名称。 And the name is generated as target class name + "$$Lambda$" + a counter . 并且名称生成为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

I found a cool solution by benjiweber . 我找到了benjiweber的一个很酷的解决方案。 It boils down to serializing the lamda to a java.lang.invoke.SerializedLambda and then get its declaring class: 它归结为将lamda序列化为java.lang.invoke.SerializedLambda ,然后获取其声明类:

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. 这是很多代码,但我的机器上的开销大约是0.003毫秒,这对大多数用例来说都没问题。

And you can do other cool stuff like: 你可以做其他很酷的东西,比如:

Map<String, String> hash = hash(
    hello -> "world",
    bob -> "was here"
);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM