Clojure - 使用可能不可用的 Java 类 编译项目

Clojure - compiling project with Java classes that are potentially not available

我正在用 Clojure 封装一个 java 库。根据 java 库版本,某些 类 存在或不存在,因此如果找不到 java 类,我的库甚至可能无法编译。我的想法是用Reflector来使用类.

的字符串名称

我正在尝试做的事情的例子:

(java.time.LocalDateTime/parse "2020-01-01")

would become

(if right-version?
  (clojure.lang.Reflector/invokeStaticMethod "java.time.LocalDate" "parse" (into-array ["2020-01-01"]))

这可行,但速度慢了 20 倍。有没有更好的方法来实现同样的目标?我能否使用一个在编译时定义正确函数的宏,具体取决于底层库的版本?

谢谢,

我无法从您的问题中判断您的代码是否在每次调用 parse 方法时都使用 Reflector。如果是这样,您可以改为定义 Method 一次以供以后多次使用:

(def right-version? true)   ; set as appropriate

(def ^java.lang.reflect.Method parse-method
  (when right-version?
    (.getMethod (Class/forName "java.time.LocalDateTime")
                "parse"
                (into-array [java.lang.CharSequence]))))

(defn parse-local-date-time [s]
  (when parse-method
    (.invoke parse-method nil (into-array [s]))))
(parse-local-date-time "2020-01-01T14:30:00")
;; => #object[java.time.LocalDateTime 0x268fc120 "2020-01-01T14:30"]

我在 the Tupelo Library 中使用这个问题的宏解决方案已经 6 年多了。它允许您编写如下代码:

(defn base64-encoder []
  (if-java-1-8-plus
    (java.util.Base64/getEncoder)
    (throw (RuntimeException. "Unimplemented prior to Java 1.8: "))))

宏本身很简单:

     (defmacro if-java-1-11-plus  
       "If JVM is Java 1.11 or higher, evaluates if-form into code. Otherwise, evaluates else-form."
       [if-form else-form]
       (if (is-java-11-plus?)
         `(do ~if-form)
         `(do ~else-form)))

     (defmacro when-java-1-11-plus  
       "If JVM is Java 1.11 or higher, evaluates forms into code. Otherwise, elide forms."
       [& forms]
       (when (is-java-11-plus?)
         `(do ~@forms)))

版本测试函数看起来像

     ;-----------------------------------------------------------------------------
     ; Java version stuff
     (s/defn version-str->semantic-vec :- [s/Int]
       "Returns the java version as a semantic vector of integers, like `11.0.17` => [11 0 17]"
       [s :- s/Str]
       (let [v1 (str/trim s)
             v2 (xsecond (re-matches #"([.0-9]+).*" v1)) ; remove any suffix like on `1.8.0-b097` or `1.8.0_234`
             v3 (str/split v2 #"\.")
             v4 (mapv #(Integer/parseInt %) v3)]
         v4))

     (s/defn java-version-str :- s/Str
       [] (System/getProperty "java.version"))

     (s/defn java-version-semantic :- [s/Int]
       [] (version-str->semantic-vec (java-version-str)))

     (s/defn java-version-min? :- s/Bool
       "Returns true if Java version is at least as great as supplied string.
       Sort is by lexicographic (alphabetic) order."
       [tgt-version-str :- s/Str]
       (let [tgt-version-vec    (version-str->semantic-vec tgt-version-str)
             actual-version-vec  (java-version-semantic)
             result             (increasing-or-equal? tgt-version-vec actual-version-vec)]
         result))

    (when-not (java-version-min? "1.7")
      (throw (ex-info "Must have at least Java 1.7" {:java-version (java-version-str)})))


     (defn is-java-8-plus? [] (java-version-min? "1.8")) ; ***** NOTE: version string is still `1.8` *****
     (defn is-java-11-plus? [] (java-version-min? "11"))  
     (defn is-java-17-plus? [] (java-version-min? "17"))  

使用宏版本的好处是可以通过符号java.util.Base64正常引用Javaclass。如果没有宏,即使被 ifwhen 包裹,旧版本 Java 的编译器也会崩溃,因为符号在 if 或 [= 之前​​无法解析17=] 被评估。

由于 Java 没有宏,在这种情况下唯一的解决方法是使用字符串 "java.util.Base64" 然后 Class/forName,等等,这是尴尬和丑陋的。由于 Clojure 有宏,我们可以利用条件代码编译来避免需要强大(但笨拙)的 Java 反射 API.

不要将这些函数复制或 re-writing 到您自己的代码中,只需使用 put

[tupelo "22.05.04"]

进入你的 project.clj 就可以离开了!


P.S.

如果检测到 Java 的旧版本,则无需抛出异常。如果 Java 版本太旧,此示例将简单地省略代码:

(t/when-java-1-11-plus

  (dotest
    (throws-not? (Instant/parse "2019-02-14T02:03:04.334Z"))
    (throws-not? (Instant/parse "2019-02-14T02:03:04Z"))
    (throws-not? (Instant/parse "0019-02-14T02:03:04Z")) ; can handle really old dates w/o throwing

...)