如何在Clojure中实现Java接口

时间:2011-12-23 10:09:51

标签: java clojure

如何创建实现此接口的Clojure对象,然后从Java代码调用?

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

Doer clojureDoer = ?;

String output = clojureDoer.doSomethin(input);

5 个答案:

答案 0 :(得分:43)

reify非常适合实现接口 - proxy是重负荷,旧的和慢的,所以应该尽可能避免。实现看起来像:

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

请注意,有关使用proxy的现有答案的语法不正确,如果您决定使用代理:proxy采用隐式this参数,而不是命名的第一个参数。

答案 1 :(得分:14)

截至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");
    }
}

答案 2 :(得分:12)

使用代理

请参阅proxy宏。 Clojure Docs有一些例子。它也包含在Java Interop页面上。

(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使用的{{1}}

答案 3 :(得分:8)

对于这个问题的更一般的看法,当你需要某种Java-interop时,这个图表可能非常有用:

https://github.com/cemerick/clojure-type-selection-flowchart

答案 4 :(得分: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.