簡體   English   中英

使用 java MethodHandles 實現鴨子類型

[英]Implement duck typing using java MethodHandles

我有兩個類AB ,它們都定義了具有共同簽名的方法foo() (什么都不接受,返回無效)。 它們沒有聲明此方法的公共基礎 class(或接口)。 我想在 As 或 Bs 上調用此方法,只要他們可以響應此調用即可。 這種方法稱為鴨子打字

我知道有一條指令叫做invokedynamic

invokedynamic 指令的每個實例都稱為動態調用點。 一個動態調用點原本在一個未鏈接的state中,也就是說沒有指定調用點調用的方法。 如前所述,動態調用站點通過引導方法鏈接到方法。 動態調用站點的引導方法是編譯器為動態類型語言指定的方法,JVM 調用一次以鏈接站點。 從引導程序方法返回的 object 永久確定調用站點的行為。

所以我嘗試使用MethodHandles來實現這一點。 這是示例:

public static class A {
    public void foo() {
    }
}

public static class B {
    public void foo() {
    }
}

public static void main(String[] args) throws Throwable {
    final MethodHandle foo = MethodHandles.lookup()
            .findVirtual(A.class, "foo", MethodType.methodType(void.class));

    foo.invoke(new B());
}

當然,我有:

Exception in thread "main" java.lang.ClassCastException: Cannot cast Main$B to Main$A
    at sun.invoke.util.ValueConversions.newClassCastException(ValueConversions.java:461)
    at sun.invoke.util.ValueConversions.castReference(ValueConversions.java:456)
    at Main.main(Main.java:30)

我清楚地看到invokedynamicMethodHanle之間的區別。 我看到問題是foo MethodHandle 綁定到class A ,而不是class B 但是在這種特殊情況下,我有可能以某種方式利用invokedynamic嗎?

為什么我需要這個? 這是我的小型研究項目的一部分。 我試圖深入了解方法句柄,並且我想在從字段和方法中檢索到的注釋實例上調用通用方法。 我無法為 Java 中的注釋定義基數 class,因此我不想使用 instanceof 和 class 強制轉換鏈或使用違反訪問權限的反射檢索這些值,如果可能的話,我想實現這個鴨子類型。

謝謝。

當 VM 第一次遇到invokedynamic指令時,它會調用工廠方法或“引導程序”方法,該方法返回一個CallSite object,其目標實現了實際功能。 您可以使用MutableCallSite自己實現它,它在第一次調用時查找您的目標方法,然后將其自己的目標設置為查找的方法。

但是,這還不足以滿足您的目的。 當您遇到新的接收器類型時,您希望重新鏈接調用站點。

這是一個示例(目前僅支持findVirtual ):

class DuckTypingCallSite extends MutableCallSite {

    private static final MethodHandle MH_relink;
    private static final MethodHandle MH_isInstance;

    static {
        try {
            MH_relink = lookup().findVirtual(DuckTypingCallSite.class, "link", methodType(Object.class, Object[].class));
            MH_isInstance = lookup().findVirtual(Class.class, "isInstance", methodType(boolean.class, Object.class));
        } catch (ReflectiveOperationException e) {
            throw new InternalError(e);
        }
    }

    private final MethodHandles.Lookup lookup;
    private final String methodName;
    private final MethodType lookupType;

    private DuckTypingCallSite(MethodHandles.Lookup lookup, String methodName, MethodType lookupType) {
        super(lookupType.insertParameterTypes(0, Object.class)); // insert receiver
        this.lookup = lookup;
        this.methodName = methodName;
        this.lookupType = lookupType;
    }

    public static DuckTypingCallSite make(MethodHandles.Lookup lookup, String methodName, MethodType lookupType) {
        DuckTypingCallSite cs = new DuckTypingCallSite(lookup, methodName, lookupType);
        cs.setTarget(MH_relink.bindTo(cs).asCollector(Object[].class, cs.type().parameterCount()).asType(cs.type()));
        return cs;
    }

    public Object link(Object[] args) throws Throwable {
        Object receiver = args[0];
        Class<?> holder = receiver.getClass();
        MethodHandle target = lookup.findVirtual(holder, methodName, lookupType).asType(type());

        MethodHandle test = MH_isInstance.bindTo(holder);
        MethodHandle newTarget = guardWithTest(test, target, getTarget());
        setTarget(newTarget);

        return target.invokeWithArguments(args);
    }

}

在第一次調用之前,調用調用站點的動態調用程序將直接跳轉到link方法,該方法將查找目標方法然后調用它,並重新鏈接 DuckTypingCallSite 以基本上緩存查找的 MethodHandle,由類型保護查看。

在第一次調用之后,這實際上創建了一個 if/else,如下所示:

if (A.class.isInstance(receiver)) {
    // invoke A.foo
} else {
    // re-link
}

然后當遇到第二種類型時,它會變成這樣:

if (B.class.isInstance(receiver)) {
    // invoke B.foo
} else if (A.class.isInstance(receiver)) {
    // invoke A.foo
} else {
    // re-link
}

等等

這是一個示例用法:

public class DuckTyping {

    private static final MethodHandle MH_foo = DuckTypingCallSite.make(lookup(), "foo", methodType(void.class)).dynamicInvoker();

    private static void foo(Object receiver) {
        try {
            MH_foo.invokeExact(receiver);
        } catch (Throwable throwable) {
            throw new IllegalStateException(throwable);
        }
    }

    public static void main(String[] args) {
        foo(new A()); // prints "A.foo"
        foo(new B()); // prints "B.foo"
    }
}

class A {
    public void foo() {
        System.out.println("A.foo");
    }
}

class B {
    public void foo() {
        System.out.println("B.foo");
    }
}

暫無
暫無

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

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