首页 文章

从java调用clojure

提问于
浏览
155

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

9 回答

  • 12

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

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

    这是一个调用 tiny.jar 中的 -binomial 函数的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中的静态方法一样 .

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

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

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

    • Clojure 1.5.1

    • 莱宁根2.1.3

    • JDK 1.7.0更新25

    The 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 告诉系统使用一个名为 binomial 的静态方法创建一个名为 com.domain.tiny 的类,这个函数接受两个整数参数并返回一个double . 有两个类似命名的函数 binomial ,一个传统的Clojure函数,以及可从Java访问的 -binomial 和包装器 . 请注意函数名称 -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.
    

    The Java Part

    Leiningen有一个内置的任务, lein-javac ,应该能够帮助Java编译 . 不幸的是,它似乎在版本2.1.3中被破坏了 . 它可以't find the installed JDK and it can't找到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可能会处理混乱的编译参数和包装 .

  • 0

    从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 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中的大多数 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")
    
  • -1

    EDIT 这个答案写于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);
      }
    }
    
  • 1

    EDIT: 差不多三年前我写了这个答案 . 在Clojure 1.6中,有一个正确的API,完全是为了从Java调用Clojure . 请Alex Miller回答最新信息:https://stackoverflow.com/a/23555959/202121

    Original answer from 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代码的源文件(或编译文件)在类路径上,这就应该可以工作 .

  • 33

    我同意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获取所有代码 .

  • 149

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

    如果用例是在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] ...)))
    
  • 10

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

  • 110

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

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

相关问题