从 java 调用 clojure
大多数谷歌点击“从java调用clojure”已经过时,建议使用来编译源代码。你能不能帮助一个清晰的解释,说明如何从Java调用Clojure,假设你已经从Clojure项目中构建了一个jar并将其包含在类路径中?clojure.lang.RT
大多数谷歌点击“从java调用clojure”已经过时,建议使用来编译源代码。你能不能帮助一个清晰的解释,说明如何从Java调用Clojure,假设你已经从Clojure项目中构建了一个jar并将其包含在类路径中?clojure.lang.RT
更新:自发布此答案以来,某些可用工具已更改。在原始答案之后,有一个更新,包括有关如何使用当前工具构建示例的信息。
它并不像编译成jar并调用内部方法那么简单。不过,似乎确实有一些技巧可以使其全部工作。下面是一个可以编译成 jar 的简单 Clojure 文件的示例:
(ns com.domain.tiny
(:gen-class
:name com.domain.tiny
:methods [#^{:static true} [binomial [int int] double]]))
(defn binomial
"Calculate the binomial coefficient."
[n k]
(let [a (inc n)]
(loop [b 1
c 1]
(if (> b k)
c
(recur (inc b) (* (/ (- a b) b) c))))))
(defn -binomial
"A Java-callable wrapper around the 'binomial' function."
[n k]
(binomial n k))
(defn -main []
(println (str "(binomial 5 3): " (binomial 5 3)))
(println (str "(binomial 10042 111): " (binomial 10042 111)))
)
如果运行它,您应该看到如下内容:
(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...
这是一个 Java 程序,它调用 .-binomial
tiny.jar
import com.domain.tiny;
public class Main {
public static void main(String[] args) {
System.out.println("(binomial 5 3): " + tiny.binomial(5, 3));
System.out.println("(binomial 10042, 111): " + tiny.binomial(10042, 111));
}
}
它的输出是:
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263
第一个魔术是在语句中使用关键字。这似乎是让你访问Clojure函数所必需的,就像Java中的静态方法一样。:methods
gen-class
第二件事是创建一个可以由Java调用的包装器函数。请注意,第二个版本前面有一个破折号。-binomial
当然,Clojure罐本身必须在类路径上。此示例使用 Clojure-1.1.0 jar。
更新:此答案已使用以下工具重新测试:
Clojure部分
首先使用 Leiningen 创建一个项目和关联的目录结构:
C:\projects>lein new com.domain.tiny
现在,转到项目目录。
C:\projects>cd com.domain.tiny
在项目目录中,打开文件并对其进行编辑,使其内容如下所示。project.clj
(defproject com.domain.tiny "0.1.0-SNAPSHOT"
:description "An example of stand alone Clojure-Java interop"
:url "http://clarkonium.net/2013/06/java-clojure-interop-an-update/"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]]
:aot :all
:main com.domain.tiny)
现在,请确保所有依赖项(Clojure)都可用。
C:\projects\com.domain.tiny>lein deps
此时,您可能会看到有关下载Clojure罐的消息。
现在编辑Clojure文件,使其包含原始答案中显示的Clojure程序。(此文件是在莱宁根创建项目时创建的。C:\projects\com.domain.tiny\src\com\domain\tiny.clj
这里的大部分魔力都在命名空间声明中。它告诉系统创建一个类,用一个名为 的静态方法命名,该函数采用两个整数参数并返回一个双精度值。有两个类似名称的函数,一个传统的Clojure函数,和包装器可以从Java访问。请注意函数名称 中的连字符。默认前缀是连字符,但如果需要,可以将其更改为其他前缀。该函数只是对二项式函数进行几次调用,以确保我们获得正确的结果。为此,请编译该类并运行该程序。:gen-class
com.domain.tiny
binomial
binomial
-binomial
-binomial
-main
C:\projects\com.domain.tiny>lein run
您应该会看到原始答案中显示的输出。
现在把它包装在一个罐子里,把它放在方便的地方。也在那里复制Clojure罐子。
C:\projects\com.domain.tiny>lein jar
Created C:\projects\com.domain.tiny\target\com.domain.tiny-0.1.0-SNAPSHOT.jar
C:\projects\com.domain.tiny>mkdir \target\lib
C:\projects\com.domain.tiny>copy target\com.domain.tiny-0.1.0-SNAPSHOT.jar target\lib\
1 file(s) copied.
C:\projects\com.domain.tiny>copy "C:<path to clojure jar>\clojure-1.5.1.jar" target\lib\
1 file(s) copied.
Java 部分
Leiningen有一个内置任务,应该能够帮助Java编译。不幸的是,它似乎在版本2.1.3中被破坏了。它找不到已安装的JDK,也找不到Maven存储库。两者的路径在我的系统上都有嵌入的空间。我认为这就是问题所在。任何Java IDE也可以处理编译和打包。但是对于这篇文章,我们走老派,在命令行上做。lein-javac
首先使用原始答案中显示的内容创建文件。Main.java
编译 java 部分
javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java
现在创建一个包含一些元信息的文件,以添加到我们要构建的jar中。在 中,添加以下文本Manifest.txt
Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main
现在将它们全部打包成一个大jar文件,包括我们的Clojure程序和Clojure jar。
C:\projects\com.domain.tiny\target>jar cfm Interop.jar Manifest.txt Main.class lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
要运行该程序:
C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263
输出与Clojure单独生成的输出基本相同,但结果已转换为Java双精度值。
如前所述,Java IDE可能会处理混乱的编译参数和打包。
从Clojure 1.6.0开始,有一种新的首选方法来加载和调用Clojure函数。这种方法现在比直接调用RT更可取(并取代了这里的许多其他答案)。javadoc在这里 - 主要入口点是。clojure.java.api.Clojure
查找并调用 Clojure 函数:
IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);
中的函数会自动加载。其他命名空间可以通过以下方式加载:clojure.core
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("clojure.set"));
IFn
s 可以传递给更高阶函数,例如,下面的示例传递给 :plus
read
IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));
Clojure中的大多数s都是指函数。但是,少数是指非函数数据值。要访问这些内容,请使用 代替 :IFn
deref
fn
IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);
有时(如果使用 Clojure 运行时的其他部分),您可能需要确保 Clojure 运行时已正确初始化 - 调用 Clojure 类上的方法就足够了。如果你不需要在Clojure上调用一个方法,那么简单地让类加载就足够了(在过去,有类似的建议来加载RT类;这是现在首选):
Class.forName("clojure.java.api.Clojure")