从 java 调用 clojure
大多数“从 java 调用 clojure”的热门 google 搜索结果都已过时,建议使用 clojure.lang.RT 来编译源代码。假设您已经从 Clojure 项目构建了一个 jar 并将其包含在类路径中,您能否帮助清楚地解释如何从 Java 调用 Clojure?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
更新:自从发布此答案以来,一些可用的工具已经发生了变化。在原始答案之后,有一个更新,包括有关如何使用当前工具构建示例的信息。
它并不像编译成 jar 并调用内部方法那么简单。不过,似乎确实有一些技巧可以让这一切发挥作用。下面是一个可以编译为 jar 的简单 Clojure 文件的示例:
如果运行它,您应该看到类似以下内容:
下面是一个 Java 程序,它调用
中的
。-binomial
函数tiny.jar它的输出是:
第一个魔法是在
gen-class
语句中使用:methods
关键字。这似乎是让您访问 Clojure 函数所必需的,就像 Java 中的静态方法一样。第二件事是创建一个可以被Java调用的包装函数。请注意,
-binomial
的第二个版本前面有一个破折号。当然,Clojure jar 本身必须位于类路径上。此示例使用 Clojure-1.1.0 jar。
更新:此答案已使用以下工具重新测试:
Clojure 部分
首先创建一个项目并使用 Leiningen 关联的目录结构:
现在,切换到项目目录。
在项目目录中,打开
project.clj
文件并编辑它,内容如下所示。现在,确保所有依赖项 (Clojure) 均可用。
此时您可能会看到一条有关下载 Clojure jar 的消息。
现在编辑 Clojure 文件
C:\projects\com.domain.tiny\src\com\domain\tiny.clj
,使其包含原始答案中显示的 Clojure 程序。 (该文件是在 Leiningen 创建项目时创建的。)这里的大部分魔力都在于命名空间声明。
:gen-class
告诉系统使用一个名为binomial
的静态方法创建一个名为com.domain.tiny
的类,该函数采用两个整数参数并返回一个双精度值。有两个类似命名的函数binomial
(传统的 Clojure 函数)以及可从 Java 访问的-binomial
和包装器。请注意函数名称-binomial
中的连字符。默认前缀是连字符,但如果需要,可以将其更改为其他内容。-main
函数仅对二项式函数进行几次调用,以确保我们获得正确的结果。为此,请编译该类并运行该程序。您应该看到原始答案中显示的输出。
现在将其包装在罐子中并放在方便的地方。将 Clojure jar 也复制到那里。
Java 部分
Leiningen 有一个内置任务
lein-javac
,它应该能够帮助进行 Java 编译。不幸的是,它似乎在 2.1.3 版本中被破坏了。它找不到已安装的 JDK,也找不到 Maven 存储库。两者的路径在我的系统上都嵌入了空格。我认为这就是问题所在。任何 Java IDE 都可以处理编译和打包。但对于这篇文章,我们将采用老式方法并在命令行中进行操作。首先使用原始答案中显示的内容创建文件
Main.java
。编译 java 部分
现在创建一个包含一些元信息的文件以添加到我们想要构建的 jar 中。在
Manifest.txt
中,添加以下文本现在将其全部打包到一个大 jar 文件中,包括我们的 Clojure 程序和 Clojure jar。
运行该程序:
输出基本上与单独使用 Clojure 生成的输出相同,但结果已转换为 Java double。
如前所述,Java IDE 可能会处理混乱的编译参数和打包。
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:
If you run it, you should see something like:
And here's a Java program that calls the
-binomial
function in thetiny.jar
.It's output is:
The first piece of magic is using the
:methods
keyword in thegen-class
statement. That seems to be required to let you access the Clojure function something like static methods in Java.The second thing is to create a wrapper function that can be called by Java. Notice that the second version of
-binomial
has a dash in front of it.And of course the Clojure jar itself must be on the class path. This example used the Clojure-1.1.0 jar.
Update: This answer has been re-tested using the following tools:
The Clojure Part
First create a project and associated directory structure using Leiningen:
Now, change to the project directory.
In the project directory, open the
project.clj
file and edit it such that the contents are as shown below.Now, make sure all of the dependencies (Clojure) are available.
You may see a message about downloading the Clojure jar at this point.
Now edit the Clojure file
C:\projects\com.domain.tiny\src\com\domain\tiny.clj
such that it contains the Clojure program shown in the original answer. (This file was created when Leiningen created the project.)Much of the magic here is in the namespace declaration. The
:gen-class
tells the system to create a class namedcom.domain.tiny
with a single static method calledbinomial
, a function taking two integer arguments and returning a double. There are two similarly named functionsbinomial
, a traditional Clojure function, and-binomial
and 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-main
function 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.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.
The Java Part
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.First create the file
Main.java
with the contents shown in the original answer.To compile java part
Now create a file with some meta-information to add to the jar we want to build. In
Manifest.txt
, add the following textNow package it all up into one big jar file, including our Clojure program and the Clojure jar.
To run the program:
The output is essentially identical to that produced by Clojure alone, but the result has been converted to a Java double.
As mentioned, a Java IDE will probably take care of the messy compilation arguments and the packaging.
从 Clojure 1.6.0 开始,有一种新的首选方式来加载和调用 Clojure 函数。现在,此方法优于直接调用 RT(并取代此处的许多其他答案)。 javadoc 位于此处 - 主要入口点是
clojure .java.api.Clojure
。查找并调用 Clojure 函数:
clojure.core 中的函数会自动加载。其他命名空间可以通过 require 加载:
IFn
可以传递给更高阶的函数,例如下面的示例将plus
传递给read
:Most < Clojure 中的 code>IFn 指的是函数。然而,有一些涉及非功能数据值。要访问这些,请使用
deref
而不是fn
:有时(如果使用 Clojure 运行时的其他部分),您可能需要确保 Clojure 运行时已正确初始化 -调用 Clojure 类上的方法足以实现此目的。如果您不需要在 Clojure 上调用方法,那么只需加载类就足够了(过去有类似的建议加载 RT 类;现在这是首选):
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
.To lookup and call a Clojure function:
Functions in
clojure.core
are automatically loaded. Other namespaces can be loaded via require:IFn
s can be passed to higher order functions, e.g. the example below passesplus
toread
:Most
IFn
s in Clojure refer to functions. A few, however, refer to non-function data values. To access these, usederef
instead offn
: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):
编辑这个答案写于2010年,并在当时起作用。请参阅亚历克斯·米勒的答案以获得更现代的解决方案。
从 Java 调用什么样的代码?如果您有使用 gen-class 生成的类,则只需调用它即可。如果您想从脚本调用函数,请查看以下示例。
如果你想在 Java 内部评估字符串代码,那么你可以使用以下代码:
EDIT This answer was written in 2010, and worked at that time. See Alex Miller's answer for more modern solution.
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.
If you want to evaluate code from string, inside Java, then you can use following code:
编辑:我大约三年前写了这个答案。在 Clojure 1.6 中,有一个适当的 API 专门用于从 Java 调用 Clojure。请Alex Miller 的回答了解最新信息。
2011年的原始答案:
据我所知,最简单的方法(如果你不使用AOT编译生成类)是使用clojure.lang.RT来访问clojure中的函数。有了它,您可以模仿在 Clojure 中所做的事情(无需以特殊方式编译):
在 Java 中:
Java 中的代码有点冗长,但我希望很清楚这些代码是等效的。
只要 Clojure 和 Clojure 代码的源文件(或编译文件)位于类路径上,此操作就应该有效。
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 answer for up to date information.
Original answer from 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):
And in Java:
It is a bit more verbose in Java, but I hope it's clear that the pieces of code are equivalent.
This should work as long as Clojure and the source files (or compiled files) of your Clojure code is on the classpath.
我同意 clartaq 的答案,但我觉得初学者也可以使用:
所以我在 此博文。
Clojure 代码如下所示:
leiningen 1.7.1 项目设置如下所示:
Java 代码如下所示:
或者您也可以从 github 上的这个项目。
I agree with clartaq's answer, but I felt that beginners could also use:
So I covered all that in this blog post.
The Clojure code looks like this:
The leiningen 1.7.1 project setup looks like this:
The Java code looks like this:
Or you can also get all the code from this project on github.
这适用于 Clojure 1.5.0:
This works with Clojure 1.5.0:
如果用例是在 Java 应用程序中包含使用 Clojure 构建的 JAR,我发现为两个世界之间的接口提供一个单独的命名空间是有益的:
核心命名空间可以使用注入的实例来完成其任务:
用于测试出于目的,接口可以被存根:
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:
The core namespace can use the injected instance to accomplish its tasks:
For testing purposes, the interface can be stubbed:
也适用于 JVM 之上的其他语言的其他技术是为要调用的函数声明一个接口,然后使用“代理”函数创建实现它们的实例。
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.
在 Clojure 中,我使用 gen-class,替代方法是从 Java 编写它,如下所示。
我不知道这是否是最好的方法,但很简单。
From Clojure i use gen-class, alternative is to write it from Java like bellow.
I don't know if its the best way, but its simple.
您还可以使用 AOT 编译来创建代表 clojure 代码的类文件。请阅读 Clojure API 文档中有关编译、gen-class 和朋友的文档,了解有关如何执行此操作的详细信息,但本质上,您将创建一个为每个方法调用调用 clojure 函数的类。
另一种选择是使用新的 defprotocol 和 deftype 功能,这也需要 AOT 编译,但提供更好的性能。我还不知道如何做到这一点的细节,但邮件列表上的问题可能会解决问题。
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.
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.