Frage

Die meisten der Top-Google-Treffer für „calling clojure von java“ sind veraltet und empfehlen clojure.lang.RT mit dem Quellcode zu kompilieren. Könnten Sie Hilfe mit einer klaren Erklärung, wie Sie Clojure von Java anrufen unter der Annahme, bereits ein Glas aus dem Clojure Projekt gebaut und enthielten in der Classpath?

War es hilfreich?

Lösung

Aktualisieren : Da diese Antwort geschrieben wurde, einige der Werkzeuge zur Verfügung haben sich geändert. Nach der ursprünglichen Antwort, gibt es ein Update, einschließlich Informationen darüber, wie das Beispiel mit aktuellen Tool zu bauen.

Es ist nicht ganz so einfach, wie in ein Gefäß zusammenzustellen und um die internen Methoden aufrufen. Es scheint einige Tricks zu sein, obwohl es alle Arbeit zu machen. Hier ist ein Beispiel für eine einfache Clojure-Datei, die zu einem Glas zusammengestellt werden kann:

(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)))
)

Wenn Sie es ausführen, sollten Sie sehen, so etwas wie:

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

Und hier ist ein Java-Programm, das die -binomial Funktion im tiny.jar nennt.

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

Es ist Ausgabe lautet:

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

Das erste Stück der Magie wird mit dem :methods Schlüsselwort in der gen-class Aussage. Das scheint erforderlich sein Sie die Clojure Funktion so etwas wie statische Methoden in Java zugreifen zu lassen.

Die zweite Sache ist, eine Wrapper-Funktion zu erstellen, die von Java aufgerufen werden können. Beachten Sie, dass die zweite Version von -binomial einen Bindestrich vor ihm hat.

Und natürlich das Clojure Glas selbst muss auf dem Klassenpfad sein. Dieses Beispiel verwendet, um den Clojure-1.1.0 jar.

Aktualisieren : Diese Antwort erneut getestet wurde, um die folgenden Tools:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Update-25

Der Clojure Teil

Erstellen Sie zunächst ein Projekt und die zugehörigen Verzeichnisstruktur mit Leiningen:

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

Nun wechseln Sie in das Projektverzeichnis.

C:\projects>cd com.domain.tiny

Im Projektverzeichnis, öffnen Sie die Datei project.clj und bearbeiten sie, so dass der Inhalt wie unten dargestellt.

(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)

Nun, stellen Sie sicher, dass alle Abhängigkeiten (Clojure) zur Verfügung.

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

Sie können eine Meldung über die Clojure jar an dieser Stelle heruntergeladen werden.

Nun bearbeitet die Clojure Datei C:\projects\com.domain.tiny\src\com\domain\tiny.clj, dass sie das Clojure-Programm enthalten in der ursprünglichen Antwort gezeigt. (Diese Datei erstellt wurde, wenn Leiningen das Projekt erstellt.)

Ein großer Teil der Magie ist hier in der Namespace-Deklaration. Die :gen-class weist das System eine Klasse namens com.domain.tiny mit einer einzigen statischen Methode namens binomial zu erstellen, eine Funktion zwei Integer-Argumente zu nehmen und eine doppelte Rückkehr. Es gibt zwei ähnlich benannte Funktionen binomial, eine traditionelle Clojure-Funktion und -binomial und Wrapper zugänglich von Java. Beachten Sie den Bindestrich in den Funktionsnamen -binomial. Der Standard-Präfix ist ein Bindestrich, aber es kann sonst, wenn gewünscht, etwas geändert werden. Die -main Funktion macht nur ein paar Anrufe an die binomischen Funktion, um sicherzustellen, dass wir die richtigen Ergebnisse erhalten. Um das zu tun, kompilieren die Klasse und starten Sie das Programm.

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

Sie sollten Ausgabe sehen in der ursprünglichen Antwort gezeigt.

verpacken Nun ist es in einem Glas auf und legte es bequem irgendwo. Kopieren Sie die Clojure jar auch dort.

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.

Der Java Part

Leiningen hat eine integrierte Task, lein-javac, die mit der Java-Kompilierung Hilfe der Lage sein sollten. Leider scheint es in der Version 2.1.3, gebrochen zu werden. Es kann nicht die installierte JDK finden und es kann die Maven-Repository nicht gefunden. Die Pfade zu haben beide eingebetteten Leerzeichen auf meinem System. Ich gehe davon aus, dass das Problem ist. Jede Java IDE könnte die Kompilierung und Verpackung behandeln. Aber für diesen Beitrag, wir alte Schule zu gehen und es in der Befehlszeile zu tun.

Erstellen Sie zunächst die Datei Main.java mit den in der ursprünglichen Antwort gezeigten Inhalte.

java Teil kompilieren

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

Sie nun eine Datei mit einigen Meta-Informationen erstellen, um das Glas wir bauen wollen hinzuzufügen. In Manifest.txt, fügen Sie den folgenden Text

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

Jetzt packen sie alle nach oben in eine große JAR-Datei,einschließlich unserer Clojure-Programm und das Clojure Glas.

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

, um das Programm auszuführen:

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

Der Ausgang ist im Wesentlichen identisch zu dem allein von Clojure produziert, aber das Ergebnis auf ein Java wurde doppelt umgewandelt.

Wie bereits erwähnt, werden ein Java-IDE wahrscheinlich kümmern sich um die chaotischen Zusammenstellung Argumente und die Verpackung.

Andere Tipps

Wie von Clojure 1.6.0 gibt es eine neue bevorzugte Art und Weise zur Last und invoke Clojure Funktionen. Dieses Verfahren wird nun bevorzugt direkt zu nennen RT (und ersetzt viele der anderen Antworten hier). Die javadoc ist href="http://clojure.github.io/clojure/javadoc/clojure/java/api/package-summary.html"> -. Der Haupteinstiegspunkt clojure.java.api.Clojure ist

, um die Suche und eine Clojure-Funktion aufrufen:

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

Funktionen in clojure.core werden automatisch geladen. Andere Namensräume können über erfordern geladen werden:

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

IFns können Funktionen höherer Ordnung übergeben werden, z.B. das folgende Beispiel Pässe plus zu read:

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

Die meisten IFns in Clojure beziehen sich auf Funktionen. Einige beziehen sich jedoch auf nicht-Funktion Datenwerte. Für den Zugriff auf diesen Einsatz deref statt fn:

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

Manchmal (wenn ein anderen Teil der Laufzeit Clojure verwenden), können Sie sicherstellen müssen, dass die Clojure Laufzeit richtig initialisiert wird - eine Methode auf der Clojure-Klasse aufrufe zu diesem Zweck ausreichend ist. Wenn Sie nicht brauchen, eine Methode auf Clojure rufen dann einfach zu verursachen, die Klasse zu Last ausreichend ist (in der Vergangenheit gab es eine ähnliche Empfehlung der RT-Klasse zu laden, dies ist nun bevorzugt):

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

Bearbeiten Diese Antwort wurde im Jahr 2010 geschrieben, und zu dieser Zeit arbeitet. Siehe Alex Millers Antwort für modernere Lösung.

Welche Art von Code fordert von Java? Wenn Sie Klasse mit gen-Klasse erzeugt haben, dann einfach es nennen. Wenn Sie Funktion von Skript aufrufen wollen, dann schauen Sie auf folgende Beispiel .

Wenn Sie Code aus Zeichenfolge auswerten möchten, innerhalb Java, dann können Sie folgenden Code verwenden:

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

EDIT: Ich schrieb diese Antwort fast drei Jahren. In Clojure 1.6 gibt es eine richtige API genau für den Zweck der Clojure von Java aufrufen. Bitte Alex Millers Antwort für aktuelle Informationen.

Original Antwort von 2011:

Wie ich es sehe, ist die einfachste Art und Weise (wenn Sie nicht über eine Klasse mit AOT Kompilierung erzeugen) ist clojure.lang.RT den Zugriff auf Funktionen in clojure zu verwenden. Mit ihm können Sie imitieren, was Sie in Clojure (keine Notwendigkeit, die Dinge in besonderer Weise zu kompilieren) getan hätte:

;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure
(require 'foo.ns)
(foo.ns/bar-fn 1 2 3)

Und in 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);

Es ist ein bisschen ausführlicher in Java, aber ich hoffe, es ist klar, dass die Teile des Codes entsprechen.

Dies sollte so lange wie Clojure und die Quelldateien (oder kompilierten Dateien) Ihren Clojure Code arbeitet auf dem Classpath sind.

Ich bin damit einverstanden Antwort mit clartaq ist, aber ich fühlte, dass Anfänger auch verwenden:

  • Schritt-für-Schritt-Informationen, wie Sie tatsächlich bekommen diese läuft
  • Informationen, die für Clojure 1.3 und neuere Versionen von Leiningen aktuellen ist.
  • ein Clojure jar, dass auch eine Hauptfunktion enthält, so dass es eigenständig ausgeführt werden kann oder als Bibliothek verknüpft.

Also ich bedeckt alles, was in diese Blog-Post .

Die Clojure Code sieht wie folgt aus:

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

Die Leiningen 1.7.1 Projekt-Setup sieht wie folgt aus:

(defproject ThingOne "1.0.0-SNAPSHOT"
  :description "Hello, Clojure"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :aot [ThingOne.core]
  :main ThingOne.core)

Der Java-Code sieht wie folgt aus:

import ThingOne.*;

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

Sie können aber auch den gesamten Code von dieses Projekt auf Github bekommen.

Das funktioniert mit 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)"));
    }
}

Wenn der Anwendungsfall ist eine JAR mit Clojure in einer Java-Anwendung gebaut ist, ich habe einen eigenen Namensraum für die Schnittstelle zwischen den beiden Welten gefunden, die von Vorteile zu sein:

(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)))

Der Kern Namespace die injizierte Instanz verwenden kann, ihre Aufgaben zu erfüllen:

(ns example-app.core)

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

Für Prüfzwecke kann die Schnittstelle stubbed werden:

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

Andere Technik, die auch mit anderen Sprachen auf der JVM arbeitet, ist eine Schnittstelle für die Funktionen zu erklären, die Sie anrufen möchten und verwenden Sie dann ‚Proxy‘ Funktion Instanz erstellen, dass implemennts sie.

Sie können auch AOT Kompilierung verwenden, um Klassendateien zu erstellen, das Ihren clojure Code. Lesen Sie die Dokumentation über Kompilation, gen-Klasse und Freunde in der Clojure API-Dokumentation für die Details darüber, wie dies zu tun, aber im Wesentlichen erhalten Sie eine Klasse erstellen, dass Anrufe Clojure Funktionen für jeden Methodenaufruf.

Eine weitere Alternative ist die neue defprotocol und Deftype Funktionalität zu nutzen, die auch AOT Kompilation erfordern aber eine bessere Leistung bieten. Ich weiß nicht, die Details, wie dies noch zu tun, aber eine Frage auf der Mailing-Liste würde wahrscheinlich den Trick.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top