我正在玩Clojure(1.6)和JavaFX 8,一开始我遇到了问题。例如,这个非常简单的代码失败了:
(ns xxyyzz.core)
(gen-class :name "xxyyzz.core.App"
:extends javafx.application.Application
:prefix "app-")
(defn app-start [app stage]
(let [button (javafx.scene.control.Button.)]))
(defn launch []
(javafx.application.Application/launch xxyyzz.core.App (into-array String [])))
(defn -main []
(launch))
这是堆栈跟踪的最后一部分似乎相关:
Caused by: java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:340)
at clojure.lang.RT.classForName(RT.java:2070)
at clojure.lang.Compiler$HostExpr.maybeClass(Compiler.java:969)
at clojure.lang.Compiler$HostExpr.access$400(Compiler.java:747)
at clojure.lang.Compiler$NewExpr$Parser.parse(Compiler.java:2494)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6560)
... 48 more
Caused by: java.lang.IllegalStateException: Toolkit not initialized
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:276)
at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:271)
at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:562)
at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:524)
at javafx.scene.control.Control.<clinit>(Control.java:81)
... 55 more
我根本不会说Java,但研究这个问题似乎问题在于Clojure及其导入Java类的方式。如果我理解正确,在导入时它会运行类静态初始化程序,并且对于某些JavaFX类(在我的情况下为Button
)会崩溃。
如果没有Clojure修复,是否可以通过一些额外的Java代码修复它?
欢迎任何提示和指示!
答案 0 :(得分:3)
我找不到改变Clojure导入行为的方法,但我确实找到了几个黑客来做我需要的东西。
首先,JavaFX提供了构建器类,因此在这种特殊情况下最简洁的方法是使用ButtonBuilder
来创建新的按钮。
第二种方法是编写一个包装Button
的简单Java类,然后从Clojure那边导入包装类。在处理少量有问题的课程时,这是一个很好的解决方案。
第三种方式是在运行时导入,类似这样(感谢#clojure的人帮忙解决这个问题):
(defn import-at-runtime [name]
(.importClass (the-ns *ns*)
(clojure.lang.RT/classForName name)))
(import-at-runtime "javafx.scene.control.Button")
(let [button (eval `(new ~(symbol "javafx.scene.control.Button") ~"Button Text"))
最后,这在Clojure的Java互操作中看起来像一个丑陋的疣,如果将来可以修复它会很棒。
更新:还有clojure.lang.RT/classForNameNonLoading,但遗憾的是,从Clojure 1.6开始,它不是public
。不过在Clojure中重新实现它很容易:
(fn [^String class-name]
(Class/forName class-name false (clojure.lang.RT/baseLoader)))
稍后,可以使用clojure.lang.Reflector/invokeConstructor
实例化该类。