「Java から clojure を呼び出す」のトップ Google ヒットのほとんどは古くclojure.lang.RT
、ソース コードのコンパイルに使用することを推奨しています。Clojure プロジェクトから既に jar を作成し、それをクラスパスに含めていると仮定して、Java から Clojure を呼び出す方法の明確な説明を手伝っていただけますか?
9 に答える
更新:この回答が投稿されてから、利用可能なツールの一部が変更されました。元の回答の後に、現在のツールを使用して例を作成する方法に関する情報を含む更新があります。
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)))
)
実行すると、次のように表示されます。
(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...
-binomial
そして、これがで関数を呼び出すJavaプログラムです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));
}
}
出力は次のとおりです。
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263
最初の魔法は、ステートメントで:methods
キーワードを使用することです。gen-class
これは、Javaの静的メソッドのようなClojure関数にアクセスできるようにするために必要なようです。
2つ目は、Javaで呼び出すことができるラッパー関数を作成することです。の2番目のバージョンの-binomial
前にダッシュがあることに注意してください。
そしてもちろん、Clojurejar自体がクラスパス上にある必要があります。この例では、Clojure-1.1.0jarを使用しました。
更新:この回答は、次のツールを使用して再テストされています。
- Clojure 1.5.1
- ライニンゲン2.1.3
- JDK1.7.0アップデート25
Clojureパート
まず、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
この時点で、Clojurejarのダウンロードに関するメッセージが表示される場合があります。
C:\projects\com.domain.tiny\src\com\domain\tiny.clj
次に、元の回答に示されているClojureプログラムが含まれるようにClojureファイルを編集します。(このファイルは、Leiningenがプロジェクトを作成したときに作成されました。)
ここでの魔法の多くは、名前空間の宣言にあります。は、 2つの整数引数を取り、doubleを返す関数である、と呼ばれる単一の静的メソッドで名前が:gen-class
付けられたクラスを作成するようにシステムに指示します。同様の名前の関数が2つあります。従来のClojure関数と、Javaからアクセスできるラッパーです。関数名のハイフンに注意してください。デフォルトのプレフィックスはハイフンですが、必要に応じて別のプレフィックスに変更できます。この関数は、二項関数を2、3回呼び出して、正しい結果が得られていることを確認します。これを行うには、クラスをコンパイルしてプログラムを実行します。com.domain.tiny
binomial
binomial
-binomial
-binomial
-main
C:\projects\com.domain.tiny>lein run
元の回答に示されている出力が表示されるはずです。
今それを瓶に詰めて、それを便利な場所に置いてください。Clojurejarもそこにコピーします。
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.
Javaパート
Leiningenにはlein-javac
、Javaコンパイルを支援できるはずの組み込みタスクがあります。残念ながら、バージョン2.1.3では壊れているようです。インストールされているJDKが見つからず、Mavenリポジトリも見つかりません。両方へのパスには、システムにスペースが埋め込まれています。それが問題だと思います。どのJavaIDEでも、コンパイルとパッケージ化を処理できます。しかし、この投稿では、古い学校に行き、コマンドラインでそれを行っています。
まず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
次に、ClojureプログラムとClojurejarを含むすべてを1つの大きな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のみで生成されたものと同じですが、結果はJavadoubleに変換されています。
前述のように、JavaIDEはおそらく厄介なコンパイル引数とパッケージングを処理します。
Clojure 1.6.0 の時点で、Clojure 関数をロードして呼び出すための新しい推奨される方法があります。このメソッドは、RT を直接呼び出すよりも優先されます (ここでの他の多くの回答に取って代わります)。javadoc はこちらです。主なエントリ ポイントは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]"));
IFn
Clojure のほとんどの s は関数を参照します。ただし、関数以外のデータ値を参照するものもあります。これらにアクセスするには、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")
編集この回答は2010年に書かれ、当時は機能していました。より現代的なソリューションについては、Alex Miller の回答を参照してください。
Java からどのようなコードを呼び出していますか? gen-class で生成されたクラスがある場合は、それを呼び出すだけです。スクリプトから関数を呼び出す場合は、次の例を参照してください。
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);
}
}
編集:私はこの回答をほぼ3年前に書きました。Clojure 1.6 には、まさに Java から Clojure を呼び出すための適切な API があります。最新情報については、Alex Miller の回答を参照してください。
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 コードのソース ファイル (またはコンパイル済みファイル) がクラスパスにある限り機能します。
ユース ケースが Clojure でビルドされた JAR を Java アプリケーションに含めることである場合、2 つの世界の間のインターフェイス用に別の名前空間を用意することが有益であることがわかりました。
(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] ...)))
JVM の上で他の言語でも機能する他の手法は、呼び出したい関数のインターフェイスを宣言し、「プロキシ」関数を使用してそれらを実装するインスタンスを作成することです。
AOT コンパイルを使用して、clojure コードを表すクラス ファイルを作成することもできます。これを行う方法の詳細については、Clojure API docs のコンパイル、gen-class、および friends に関するドキュメントを参照してください。ただし、本質的には、メソッド呼び出しごとに clojure 関数を呼び出すクラスを作成します。
もう 1 つの方法は、新しい defprotocol および deftype 機能を使用することです。これも AOT コンパイルが必要ですが、パフォーマンスは向上します。これを行う方法の詳細はまだわかりませんが、メーリング リストで質問するとうまくいくでしょう。