从java调用clojure

时间:2010-02-02 03:51:11

标签: java clojure clojure-java-interop

“从java调用clojure”的大多数热门谷歌点击都已过时,建议使用clojure.lang.RT来编译源代码。如果您已经从Clojure项目中构建了一个jar并将其包含在类路径中,您能否帮助清楚解释如何从Java调用Clojure?

9 个答案:

答案 0 :(得分:155)

更新:由于此答案已发布,因此部分可用工具已更改。在原始答案之后,有一个更新,包括有关如何使用当前工具构建示例的信息。

它不像编译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."
  [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...

这是一个调用-binomialtiny.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

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

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

当然Clojure jar本身必须在类路径上。这个例子使用了Clojure-1.1.0 jar。

更新:此答案已使用以下工具重新测试:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Update 25

Clojure Part

首先使用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 jar的消息。

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

这里的大部分魔力都在名称空间声明中。 :gen-class告诉系统使用一个名为com.domain.tiny的静态方法创建一个名为binomial的类,一个函数接受两个整数参数并返回一个double。有两个类似命名的函数binomial,一个传统的Clojure函数,以及-binomial和可从Java访问的包装器。请注意函数名-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有一个内置任务lein-javac,它应该能够帮助Java编译。不幸的是,它似乎在版本2.1.3中被破坏了。它无法找到已安装的JDK,也无法找到Maven存储库。两者的路径都在我的系统上嵌入了空间。我认为这是问题所在。任何Java IDE都可以处理编译和打包。但对于这篇文章,我们正在上学并在命令行上这样做。

首先使用原始答案中显示的内容创建文件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 double。

如前所述,Java IDE可能会处理混乱的编译参数和包装。

答案 1 :(得分:113)

从Clojure 1.6.0开始,有一种新的首选方法来加载和调用Clojure函数。此方法现在更倾向于直接调用RT(并在此处取代许多其他答案)。 javadoc是here - 主要入口点是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传递给read

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

Clojure中的大多数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") 

答案 2 :(得分:34)

编辑这个答案写于2010年,当时工作。请参阅Alex Miller对更现代化解决方案的回答。

从Java调用什么样的代码?如果你有使用gen-class生成的类,那么只需调用它。如果要从脚本调用函数,请查看following example

如果要在Java中评估字符串中的代码,则可以使用以下代码:

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 [a b]   (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);
  }
}

答案 3 :(得分:12)

编辑:差不多三年前我写了这个答案。在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代码的源文件(或编译文件)在类路径上,这就可以工作。

答案 4 :(得分:10)

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

  • 有关如何实际运行的逐步信息
  • 当前Clojure 1.3和leiningen最新版本的信息。
  • 一个Clojure jar,它还包含一个main函数,因此它可以独立运行链接为库。

所以我在this blog post中涵盖了所有内容。

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);
    }
}

或者您也可以从this project on github获取所有代码。

答案 5 :(得分:3)

这适用于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)"));
    }
}

答案 6 :(得分:1)

如果用例是在Java应用程序中包含使用Clojure构建的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)] ...)) 

出于测试目的,界面可以是存根的:

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

答案 7 :(得分:0)

与JVM之上的其他语言一起使用的其他技术是为要调用的函数声明一个接口,然后使用'proxy'函数创建实现它们的实例。

答案 8 :(得分:-1)

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

另一种选择是使用新的defprotocol和deftype功能,这也需要AOT编译,但提供更好的性能。我还不知道如何执行此操作的详细信息,但邮件列表上的问题可能会解决问题。