从java调用clojure

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2181774/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:29:10  来源:igfitidea点击:

Calling clojure from java

javaclojureclojure-java-interop

提问by Arthur Ulfeldt

Most of the top google hits for "calling clojure from java" are outdated and recommend using clojure.lang.RTto compile the source code. Could you help with a clear explanation of how to call Clojure from Java assuming you have already built a jar from the Clojure project and included it in the classpath?

“从 Java 调用 clojure”的大部分谷歌热门搜索词已经过时,建议使用它clojure.lang.RT来编译源代码。假设您已经从 Clojure 项目构建了一个 jar 并将其包含在类路径中,您能否帮助清楚地解释如何从 Java 调用 Clojure?

采纳答案by clartaq

Update: Since this answer was posted, some of the tools available have changed. After the original answer, there is an update including information on how to build the example with current tools.

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

It isn't quite as simple as compiling to a jar and calling the internal methods. There do seem to be a few tricks to make it all work though. Here's an example of a simple Clojure file that can be compiled to a jar:

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

If you run it, you should see something like:

如果你运行它,你应该看到如下内容:

(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...

And here's a Java program that calls the -binomialfunction in the tiny.jar.

而这里的调用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));
    }
}

It's output is:

它的输出是:

(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The first piece of magic is using the :methodskeyword in the gen-classstatement. That seems to be required to let you access the Clojure function something like static methods in Java.

第一个魔法是:methodsgen-class语句中使用关键字。这似乎是让您访问 Clojure 函数(类似于 Java 中的静态方法)所必需的。

The second thing is to create a wrapper function that can be called by Java. Notice that the second version of -binomialhas a dash in front of it.

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

And of course the Clojure jar itself must be on the class path. This example used the Clojure-1.1.0 jar.

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

Update: This answer has been re-tested using the following tools:

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

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Update 25
  • Clojure 1.5.1
  • 莱宁根 2.1.3
  • JDK 1.7.0 更新 25

The Clojure Part

Clojure 部分

First create a project and associated directory structure using Leiningen:

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

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

Now, change to the project directory.

现在,切换到项目目录。

C:\projects>cd com.domain.tiny

In the project directory, open the project.cljfile and edit it such that the contents are as shown below.

在工程目录下,打开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)

Now, make sure all of the dependencies (Clojure) are available.

现在,确保所有依赖项 (Clojure) 都可用。

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

You may see a message about downloading the Clojure jar at this point.

此时您可能会看到一条关于下载 Clojure jar 的消息。

Now edit the Clojure file C:\projects\com.domain.tiny\src\com\domain\tiny.cljsuch that it contains the Clojure program shown in the original answer. (This file was created when Leiningen created the project.)

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

Much of the magic here is in the namespace declaration. The :gen-classtells the system to create a class named com.domain.tinywith a single static method called binomial, a function taking two integer arguments and returning a double. There are two similarly named functions binomial, a traditional Clojure function, and -binomialand wrapper accessible from Java. Note the hyphen in the function name -binomial. The default prefix is a hyphen, but it can be changed to something else if desired. The -mainfunction just makes a couple of calls to the binomial function to assure that we are getting the correct results. To do that, compile the class and run the program.

这里的大部分魔法都在名称空间声明中。该:gen-class告诉系统创建一个名为类com.domain.tiny有称为单一的静态方法binomial,一个函数接受两个整数参数并返回一个double。有两个名称相似的函数binomial,一个是传统的 Clojure函数,另一个是-binomial可从 Java 访问的包装器。请注意函数名称中的连字符-binomial。默认前缀是连字符,但如果需要,可以将其更改为其他内容。该-main函数只是对二项式函数进行了几次调用,以确保我们得到正确的结果。为此,请编译该类并运行该程序。

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

You should see output shown in the original answer.

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

Now package it up in a jar and put it someplace convenient. Copy the Clojure jar there too.

现在把它装在一个罐子里,放在方便的地方。也将 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

Java 部分

Leiningen has a built-in task, lein-javac, that should be able to help with the Java compilation. Unfortunately, it seems to be broken in version 2.1.3. It can't find the installed JDK and it can't find the Maven repository. The paths to both have embedded spaces on my system. I assume that is the problem. Any Java IDE could handle the compilation and packaging too. But for this post, we're going old school and doing it at the command line.

Leiningen 有一个内置任务 ,lein-javac应该能够帮助进行 Java 编译。不幸的是,它似乎在 2.1.3 版本中被破坏了。它找不到已安装的 JDK,也找不到 Maven 存储库。两者的路径都在我的系统上嵌入了空格。我认为这就是问题所在。任何 Java IDE 也可以处理编译和打包。但是对于这篇文章,我们将采用传统方法并在命令行中进行操作。

First create the file Main.javawith the contents shown in the original answer.

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

To compile java part

编译java部分

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

Now create a file with some meta-information to add to the jar we want to build. In Manifest.txt, add the following text

现在创建一个包含一些元信息的文件,以添加到我们要构建的 jar 中。在 中Manifest.txt,添加以下文本

Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main

Now package it all up into one big jar file, including our Clojure program and the Clojure jar.

现在将它们全部打包成一个大的 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

To run the program:

运行程序:

C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The output is essentially identical to that produced by Clojure alone, but the result has been converted to a Java double.

输出与单独由 Clojure 生成的输出基本相同,但结果已转换为 Java 双精度值。

As mentioned, a Java IDE will probably take care of the messy compilation arguments and the packaging.

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

回答by Alex Ott

EDITThis answer was written in 2010, and worked at that time. See Alex Miller's answer for more modern solution.

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

What kind of code are calling from Java? If you have class generated with gen-class, then simply call it. If you want to call function from script, then look to following example.

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

If you want to evaluate code from string, inside Java, then you can use following code:

如果要在 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);
  }
}

回答by rosejn

You can also use AOT compilation to create class files representing your clojure code. Read the documentation about compilation, gen-class and friends in the Clojure API docs for the details about how to do this, but in essence you will create a class that calls clojure functions for each method invocation.

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

Another alternative is to use the new defprotocol and deftype functionality, which will also require AOT compilation but provide better performance. I don't know the details of how to do this yet, but a question on the mailing list would probably do the trick.

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

回答by Petr Gladkikh

Other technique that works also with other languages on top of JVM is to declare an interface for functions you want to call and then use 'proxy' function to create instance that implemennts them.

在 JVM 之上也适用于其他语言的其他技术是为您要调用的函数声明一个接口,然后使用“代理”函数创建实现它们的实例。

回答by raek

EDIT:I wrote this answer almost three years ago. In Clojure 1.6 there is a proper API exactly for the purpose of calling Clojure from Java. Please Alex Miller's answerfor up to date information.

编辑:我大约三年前写了这个答案。在 Clojure 1.6 中有一个合适的 API,专门用于从 Java 调用 Clojure。请Alex Miller 回答以获取最新信息。

Original answer from 2011:

2011年的原始答案:

As I see it, the simplest way (if you don't generate a class with AOT compilation) is to use clojure.lang.RT to access functions in clojure. With it you can mimic what you would have done in Clojure (no need to compile things in special ways):

在我看来,最简单的方法(如果您不使用 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)

And in Java:

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

It is a bit more verbose in Java, but I hope it's clear that the pieces of code are equivalent.

在 Java 中它有点冗长,但我希望很清楚这些代码段是等效的。

This should work as long as Clojure and the source files (or compiled files) of your Clojure code is on the classpath.

只要 Clojure 和 Clojure 代码的源文件(或编译文件)在类路径上,这应该有效。

回答by sandover

I agree with clartaq's answer, but I felt that beginners could also use:

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

  • step-by-step information on how to actually get this running
  • information that's current for Clojure 1.3 and recent versions of leiningen.
  • a Clojure jar that also includes a main function, so it can be run standalone orlinked as a library.
  • 有关如何实际运行的分步信息
  • Clojure 1.3 和 leiningen 最新版本的最新信息。
  • 一个 Clojure jar,还包含一个 main 函数,因此它可以独立运行作为库链接。

So I covered all that in this blog post.

所以我在这篇博文中涵盖了所有这些。

The Clojure code looks like this:

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." ))

The leiningen 1.7.1 project setup looks like this:

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)

The Java code looks like this:

Java 代码如下所示:

import ThingOne.*;

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

Or you can also get all the code from this project on github.

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

回答by KIM Taegyoon

This works with Clojure 1.5.0:

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

回答by Alex Miller

As of Clojure 1.6.0, there is a new preferred way to load and invoke Clojure functions. This method is now preferred to calling RT directly (and supersedes many of the other answers here). The javadoc is here- the main entry point is clojure.java.api.Clojure.

从 Clojure 1.6.0 开始,有一种新的首选方式来加载和调用 Clojure 函数。这种方法现在比直接调用 RT 更受欢迎(并取代了此处的许多其他答案)。javadoc 在这里- 主要入口点是clojure.java.api.Clojure.

To lookup and call a Clojure function:

查找和调用 Clojure 函数:

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);

Functions in clojure.coreare automatically loaded. Other namespaces can be loaded via require:

中的函数clojure.core会自动加载。其他命名空间可以通过 require 加载:

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

IFns can be passed to higher order functions, e.g. the example below passes plusto read:

IFns 可以传递给高阶函数,例如下面的例子传递plusread

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

Most IFns in Clojure refer to functions. A few, however, refer to non-function data values. To access these, use derefinstead of fn:

IFnClojure 中的大多数s 指的是函数。但是,有一些是指非功能数据值。要访问这些,请使用deref代替fn

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

Sometimes (if using some other part of the Clojure runtime), you may need to ensure that the Clojure runtime is properly initialized - calling a method on the Clojure class is sufficient for this purpose. If you do not need to call a method on Clojure, then simply causing the class to load is sufficient (in the past there has been a similar recommendation to load the RT class; this is now preferred):

有时(如果使用 Clojure 运行时的其他部分),您可能需要确保 Clojure 运行时正确初始化 - 调用 Clojure 类上的方法就足以达到此目的。如果你不需要在 Clojure 上调用一个方法,那么简单地使类加载就足够了(过去也有类似的建议加载 RT 类;现在首选):

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

回答by jstaffans

If the use case is to include a JAR built with Clojure in a Java application, I have found having a separate namespace for the interface between the two worlds to be beneficial:

如果用例是在 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)))

The core namespace can use the injected instance to accomplish its tasks:

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

(ns example-app.core)

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

For testing purposes, the interface can be stubbed:

出于测试目的,接口可以被存根:

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