我该如何在Clojure中实现Java接口

23

我该如何创建一个实现此接口的Clojure对象,并且让它可以从Java代码中调用?

public interface Doer {
   public String doSomethin(String input);
}

Doer clojureDoer = ?;

String output = clojureDoer.doSomethin(input);

感谢大家的帮助!最终我使用了Reify,并在Clojure中编写了主要函数。Clojure非常酷! - user468687
5个回答

50

reify 更适合用来实现接口 - proxy 过于笨重、老旧和缓慢,所以应尽可能避免使用。实现的代码如下:

(reify Doer
  (doSomethin [this input]
    (...whatever...)))

19

截至 Clojure 1.6 版本,推荐的方法如下所示。假设您的类路径中包含 Clojure 1.6 的 jar 文件和以下 clojure 文件(或其已编译等效物):

(ns my.clojure.namespace
  (:import [my.java.package Doer]))

(defn reify-doer
  "Some docstring about what this specific implementation of Doer
  does differently than the other ones. For example, this one does
  not actually do anything but print the given string to stdout."
  []
  (reify
    Doer
    (doSomethin [this in] (println in))))

那么,从Java中,您可以通过以下方式访问它:

package my.other.java.package.or.maybe.the.same.one;

import my.java.package.Doer;
import clojure.lang.IFn;
import clojure.java.api.Clojure;

public class ClojureDoerUser {
    // First, we need to instruct the JVM to compile/load our
    // Clojure namespace. This should, obviously, only be done once.
    static {
        IFn require = Clojure.var("clojure.core", "require");
        require.invoke(Clojure.read("my.clojure.namespace"));
        // Clojure.var() does a somewhat expensive lookup; if we had more than
        // one Clojure namespace to load, so as a general rule its result should
        // always be saved into a variable.
        // The call to Clojure.read is necessary because require expects a Clojure
        // Symbol, for which there is no more direct official Clojure API.
    }

    // We can now lookup the function we want from our Clojure namespace.
    private static IFn doerFactory = Clojure.var("my.clojure.namespace", "reify-doer");

    // Optionally, we can wrap the doerFactory IFn into a Java wrapper,
    // to isolate the rest of the code from our Clojure dependency.
    // And from the need to typecast, as IFn.invoke() returns Object.
    public static Doer createDoer() {
        return (Doer) doerFactory.invoke();
    }
    public static void main(String[] args) {
        Doer doer = (Doer) doerFactory.invoke();
        doer.doSomethin("hello, world");
    }
}

感谢@Gary Verhaegen ;) - Cedric H.

15

使用代理

参见proxy宏。 Clojure文档中有一些示例。此外,Java互操作页面也涵盖了此内容。

(proxy [Doer] []
  (doSomethin [input]
    (str input " went through proxy")))

proxy 返回一个实现 Doer 接口的对象。若要从 Java 中访问该对象,您需要使用 gen-class 使您的 Clojure 代码可从 Java 调用。这在回答 "Calling clojure from java"问题中有所涉及。

使用 gen-class

(ns doer-clj
  (:gen-class
    :name DoerClj
    :implements [Doer]
    :methods [[doSomethin [String] String]]))

(defn -doSomethin
  [_ input]
  (str input " went through Clojure"))

现在将其保存为doer_clj.cljmkdir classes并通过在你的REPL中调用(require 'doer-clj) (compile 'doer-clj)进行编译。您应该会发现DoerClj.class已经准备好在classes目录中供Java使用。


请注意我的问题中的"Doer clojureDoer = ?"。我应该在"?"处放置什么才能使程序正常工作?您提供的链接展示了如何将Clojure对象导入为静态类。谢谢! - user468687
在这种情况下,您可能更感兴趣于我刚刚添加的答案的后半部分。它是“无代理”的,并且似乎更适合您的情况。如果第二个解决方案可以解决您的问题,我想我会删除第一个解决方案。 - Jan
有趣的是,我不得不将Doer放入一个包中,因为编译器否则会寻找java.lang.Doer。当我执行Doer doer = new DoerClj()时,我遇到了一个异常:Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file DoerClj。 - user468687
请澄清一下 - 您使用的是哪个Clojure版本? - Jan
奇怪。尝试编译基于Runnable接口的这个简单示例。当我(require 'doer-clj) (compile 'doer-clj)时,我可以毫无问题地调用DoerClj.run() - Jan
Clojure 1.3.0 稳定版已经发布。 - semperos

10

我以前遇到过这个问题,但我没有意识到它真的是一个很好的资源。谢谢你指出来。 - BillRobertson42

0
如果你的接口中定义了doSomethin(),那么在:methods中就不应该提及它。引用自http://clojuredocs.org/clojure_core/clojure.core/gen-class
:methods [ [name [param-types] return-type], ...]
The generated class automatically defines all of the non-private
methods of its superclasses/interfaces. This parameter can be used
to specify the signatures of additional methods of the generated
class. Static methods can be specified with ^{:static true} in the
signature's metadata. Do not repeat superclass/interface signatures
here.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接