簡體   English   中英

獲取Java lambda表達式的封閉類

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

我有一個采用功能參數的方法,例如Runnable 由於它是一個庫方法,我希望它使用從函數參數派生的記錄器。 在函數參數上調用getClass適用於普通類,並且我可以為嵌套或匿名類獲取getEnclosingClass ; 但如果它是一個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
    }
}

正如你所看到的那樣,這不是很優雅,也可能不便攜。 我嘗試過getEnclosingClassgetEnclosingMethodgetEnclosingConstructor ,但它們都返回null

有任何想法嗎?

正如Tassos Bassoukos已經提到的,它是設計的。

Lambda(類)的字節碼是在運行時生成的。 所以你得到的是班級的實際名稱。 並且名稱生成為target class name + "$$Lambda$" + a counter

找一個小片段進行演示。

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();
    }
}

產量

--- 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

我找到了benjiweber的一個很酷的解決方案。 它歸結為將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);
}

這是很多代碼,但我的機器上的開銷大約是0.003毫秒,這對大多數用例來說都沒問題。

你可以做其他很酷的東西,比如:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM