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。如果没有宏,即使被 if
或 when
包裹,旧版本 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
...)
我正在用 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。如果没有宏,即使被 if
或 when
包裹,旧版本 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
...)