在 Clojure 中使用 java.lang.invoke.MethodHandle

using java.lang.invoke.MethodHandle in clojure

我正在学习这里的教程:https://www.baeldung.com/java-method-handles

在 clojure 中,我有一个简单的例子:

(import (java.lang.invoke MethodHandles
                          MethodHandles$Lookup
                          MethodType
                          MethodHandle))

(defonce +lookup+ (MethodHandles/lookup))

(def ^MethodHandle concat-handle (.findVirtual +lookup+
                                 String
                                 "concat"
                                 (MethodType/methodType String String)))

(.invokeExact concat-handle (into-array Object ["hello" "there"]))

这给出了一个错误:

Unhandled java.lang.invoke.WrongMethodTypeException
 expected (String,String)String but found (Object[])Object

         Invokers.java:  476  java.lang.invoke.Invokers/newWrongMethodTypeException
         Invokers.java:  485  java.lang.invoke.Invokers/checkExactType
                  REPL:   26  hara.object.handle/eval17501
                  REPL:   26  hara.object.handle/eval17501
         Compiler.java: 7062  clojure.lang.Compiler/eval
         Compiler.java: 7025  clojure.lang.Compiler/eval
              core.clj: 3206  clojure.core/eval
              core.clj: 3202  clojure.core/eval
              main.clj:  243  clojure.main/repl/read-eval-print/f

有没有办法让 invoke 工作?

您可以使用 .invokeWithArguments,它将根据提供的参数计算出正确的元数:

(.invokeWithArguments concat-handle (object-array ["hello" "there"]))
=> "hellothere"

或者您可以使用 .invoke,但您需要 MethodHandle.asSpreader 将可变参数正确应用到具有固定元数的 String.concat

(def ^MethodHandle other-handle
  (.asSpreader
    concat-handle
    (Class/forName "[Ljava.lang.String;") ;; String[].class
    2))

(.invoke other-handle (into-array String ["hello" "there"]))
=> "hellothere"

如果可能的话,我不确定如何使用 Clojure 中的 .invokeExact 来完成这项工作。

The symbolic type descriptor at the call site of invokeExact must exactly match this method handle's type. No conversions are allowed on arguments or return values.

This answer.invoke.invokeExact的限制有更多解释。

一些基于@TaylorWood 回答的有趣基准:

(with-out-str
  (time (dotimes [i 1000000]
          (.concat "hello" "there"))))
=> "\"Elapsed time: 8.542214 msecs\"\n"


(with-out-str
  (def concat-fn (fn [a b] (.concat a b)))
  (time (dotimes [i 1000000]
          (concat-fn "hello" "there"))))
=> "\"Elapsed time: 3600.357352 msecs\"\n"

(with-out-str
  (def concat-anno (fn [^String a b] (.concat a b)))
  (time (dotimes [i 1000000]
          (concat-anno "hello" "there"))))
=> "\"Elapsed time: 16.461237 msecs\"\n"

(with-out-str
  (def concat-reflect (.? String "concat" :#))
  (time (dotimes [i 1000000]
          (concat-reflect "hello" "there"))))
=> "\"Elapsed time: 1804.522226 msecs\"\n"

(with-out-str
  (def ^MethodHandle concat-handle
    (.findVirtual +lookup+
                  String
                  "concat"
                  (MethodType/methodType String String)))
  (time (dotimes [i 1000000]
          (.invokeWithArguments concat-handle (into-array Object ["hello" "there"])))))
=> "\"Elapsed time: 1974.824815 msecs\"\n" 

(with-out-str
  (def ^MethodHandle concat-spread
    (.asSpreader concat-handle
                 (Class/forName "[Ljava.lang.String;") ;; String[].class
                 2))
  (time (dotimes [i 1000000]
          (.invoke other-handle (into-array String ["hello" "there"])))))
=> "\"Elapsed time: 399.779913 msecs\"\n"