从java调用clojure

大多数的“从Java调用clojure”的谷歌命中已过时,build议使用clojure.lang.RT来编译源代码。 如果你已经从Clojure项目中构build了一个jar并将其包含在classpath中,那么你能否帮忙清楚地说明如何从Java调用Clojure?

更新 :由于这个答案已经发布,一些可用的工具已经改变。 原始答案后,有一个更新,包括有关如何使用当前工具构build示例的信息。

这不像编译成jar和调用内部方法那么简单。 似乎有一些技巧,使所有的工作。 下面是一个简单的Clojure文件的例子,可以编译成jar文件:

 (ns com.domain.tiny (:gen-class :name com.domain.tiny :methods [#^{:static true} [binomial [int int] double]])) (defn binomial "Calculate the binomial coefficient." [nk] (let [a (inc n)] (loop [b 1 c 1] (if (> bk) c (recur (inc b) (* (/ (- ab) b) c)))))) (defn -binomial "A Java-callable wrapper around the 'binomial' function." [nk] (binomial nk)) (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... 

这里是一个调用-binomial中的tiny.jar函数的Java程序。

 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 

第一块魔法是在gen-class语句中使用:methods关键字。 这似乎是需要让你访问Clojure函数,就像Java中的静态方法。

第二件事是创build一个可以被Java调用的包装函数。 注意第二版的-binomial在它前面有一个破折号。

当然,Clojurejar子本身也必须在class级路线上。 这个例子使用了Clojure-1.1.0 jar。

更新 :使用以下工具重新testing了此答案:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0更新25

Clojure部分

首先使用Leiningen创build一个项目和相关的目录结构:

 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 jar的消息。

现在编辑Clojure文件C:\projects\com.domain.tiny\src\com\domain\tiny.clj ,使其包含原始答案中显示的Clojure程序。 (这个文件是Leiningen创build项目时创build的。)

这里的大部分魔法都在命名空间声明中。 :gen-class告诉系统用一个叫做binomial静态方法创build一个名为com.domain.tiny的类,这个函数带有两个整数参数并返回一个double。 有两个类似命名的函数binomial ,一个传统的Clojure函数,以及可从Java访问的-binomial和wrapper。 请注意函数名称中的连字符 – -binomial 。 默认的前缀是一个连字符,但如果需要的话,可以将其改为别的。 -main函数只是对二项函数进行一些调用,以确保我们得到正确的结果。 要做到这一点,编译类和运行程序。

 C:\projects\com.domain.tiny>lein run 

您应该看到原始答案中显示的输出。

现在把它装在一个jar子里,放在方便的地方。 把Clojure jar也复制到那里。

 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有一个内置的任务lein-javac ,它应该能够帮助Java编译。 不幸的是,它似乎在2.1.3版本中被打破了。 它找不到安装的JDK,并找不到Maven仓库。 两者的path在我的系统上都有embedded的空间。 我认为这是问题。 任何Java IDE都可以处理编译和打包。 但是对于这篇文章,我们正在上学,并在命令行上进行。

首先使用原始答案中显示的内容创buildMain.java文件。

编译java部分

 javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java 

现在创build一个包含一些元信息的文件,添加到我们想要构build的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 double。

如前所述,一个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中的函数会自动加载。 其他名称空间可以通过require加载:

 IFn require = Clojure.var("clojure.core", "require"); require.invoke(Clojure.read("clojure.set")); 

IFn可以传递给更高阶的函数,例如下面的例子传递给plus

 IFn map = Clojure.var("clojure.core", "map"); IFn inc = Clojure.var("clojure.core", "inc"); map.invoke(inc, Clojure.read("[1 2 3]")); 

Clojure中的大多数IFn都是指函数。 但是,有一些是指非function数据值。 要访问这些,请使用deref而不是fn

 IFn printLength = Clojure.var("clojure.core", "*print-length*"); IFn deref = Clojure.var("clojure.core", "deref"); deref.invoke(printLength); 

有时(如果使用Clojure运行时的其他部分),您可能需要确保Clojure运行时已正确初始化 – 在Clojure类上调用一个方法就足够了。 如果你不需要在Clojure上调用一个方法,那么简单地导致类加载就足够了(过去也有类似的build议来加载RT类,现在这是首选):

 Class.forName("clojure.java.api.Clojure") 

编辑这个答案写在2010年,并在那个时候工作。 看到亚历克斯米勒的答案更现代的解决scheme。

从Java调用什么样的代码? 如果你有用gen-class生成的类,那么就简单地调用它。 如果你想从脚本调用函数,那么看下面的例子 。

如果您想从string中评估代码,那么您可以使用以下代码:

 import clojure.lang.RT; import clojure.lang.Var; import clojure.lang.Compiler; import java.io.StringReader; public class Foo { public static void main(String[] args) throws Exception { // Load the Clojure script -- as a side effect this initializes the runtime. String str = "(ns user) (defn foo [ab] (str a \" \" b))"; //RT.loadResourceScript("foo.clj"); Compiler.load(new StringReader(str)); // Get a reference to the foo function. Var foo = RT.var("user", "foo"); // Call it! Object result = foo.invoke("Hi", "there"); System.out.println(result); } } 

编辑:我几乎三年前写了这个答案。 在Clojure 1.6中,恰好有一个恰当的API用于从Java调用Clojure。 请Alex Miller的最新信息的答案: https : //stackoverflow.com/a/23555959/202121

2011年原始答案:

就我所见,最简单的方法(如果你不用AOT编译生成一个类)就是使用clojure.lang.RT来访问clojure中的函数。 有了它,你可以模仿你在Clojure中所做的事情(不需要用特殊的方式编译):

 ;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure (require 'foo.ns) (foo.ns/bar-fn 1 2 3) 

在Java中:

 // Example usage of the "bar-fn" function from the "foo.ns" namespace from Java import clojure.lang.RT; import clojure.lang.Symbol; ... RT.var("clojure.core", "require").invoke(Symbol.intern("foo.ns")); RT.var("foo.ns", "bar-fn").invoke(1, 2, 3); 

这在Java中有点冗长,但是我希望很明显这些代码段是等价的。

只要Clojure和Clojure代码的源文件(或编译文件)位于类path中,就应该这样工作。

我同意clartaq的回答,但我觉得初学者也可以使用:

  • 一步一步地了解如何实际运行
  • 目前Clojure 1.3和最新版本的Leiningen的信息。
  • 一个Clojure jar也包含一个main函数,所以它可以独立运行链接为一个库。

所以我在这个博客文章中涵盖了所有内容。

Clojure代码如下所示:

 (ns ThingOne.core (:gen-class :methods [#^{:static true} [foo [int] void]])) (defn -foo [i] (println "Hello from Clojure. My input was " i)) (defn -main [] (println "Hello from Clojure -main." )) 

leiningen 1.7.1项目设置看起来像这样:

 (defproject ThingOne "1.0.0-SNAPSHOT" :description "Hello, Clojure" :dependencies [[org.clojure/clojure "1.3.0"]] :aot [ThingOne.core] :main ThingOne.core) 

Java代码如下所示:

 import ThingOne.*; class HelloJava { public static void main(String[] args) { System.out.println("Hello from Java!"); core.foo (12345); } } 

或者你也可以在github上获得这个项目的所有代码。

这适用于Clojure 1.5.0:

 public class CljTest { public static Object evalClj(String a) { return clojure.lang.Compiler.load(new java.io.StringReader(a)); } public static void main(String[] args) { new clojure.lang.RT(); // needed since 1.5.0 System.out.println(evalClj("(+ 1 2)")); } } 

如果用例是在Java应用程序中包含一个使用Clojure构build的JAR,我发现为这个两个世界之间的接口分配一个名称空间是有益的:

 (ns example-app.interop (:require [example-app.core :as core]) ;; This example covers two-way communication: the Clojure library ;; relies on the wrapping Java app for some functionality (through ;; an interface that the Clojure library provides and the Java app ;; implements) and the Java app calls the Clojure library to perform ;; work. The latter case is covered by a class provided by the Clojure lib. ;; ;; This namespace should be AOT compiled. ;; The interface that the java app can implement (gen-interface :name com.example.WeatherForecast :methods [[getTemperature [] Double]]) ;; The class that the java app instantiates (gen-class :name com.example.HighTemperatureMailer :state state :init init ;; Dependency injection - take an instance of the previously defined ;; interface as a constructor argument :constructors {[com.example.WeatherForecast] []} :methods [[sendMails [] void]]) (defn -init [weather-forecast] [[] {:weather-forecast weather-forecast}]) ;; The actual work is done in the core namespace (defn -sendMails [this] (core/send-mails (.state this))) 

核心名称空间可以使用注入的实例来完成其任务:

 (ns example-app.core) (defn send-mails [{:keys [weather-forecast]}] (let [temp (.getTemperature weather-forecast)] ...)) 

出于testing目的,可以将接口插入:

 (example-app.core/send-mails (reify com.example.WeatherForecast (getTemperature [this] ...))) 

其他与JVM之上的其他语言一起工作的技术是为要调用的函数声明一个接口,然后使用“代理”函数创build实现它们的实例。

您也可以使用AOT编译来创build代表您的clojure代码的类文件。 在Clojure API文档中阅读有关编译,gen-class和朋友的文档,了解有关如何执行此操作的详细信息,但本质上,您将创build一个为每个方法调用调用clojure函数的类。

另一个select是使用新的defprotocol和deftypefunction,这也将需要AOT编译,但提供更好的性能。 我不知道如何做到这一点的细节,但在邮件列表上的问题可能会伎俩。