Вопрос

Большинство главных Google Hits для «вызова Clojure из Java» устарели и рекомендуют использовать clojure.lang.RT Чтобы скомпилировать исходный код. Можете ли вы помочь с ясным объяснением того, как позвонить Clojure от Java, предполагая, что вы уже построили банку из проекта Clojure и включали его в классную патрубку?

Это было полезно?

Решение

Обновлять: Поскольку этот ответ был опубликован, некоторые из доступных инструментов изменились. После первоначального ответа есть обновление, включая информацию о том, как создать пример с текущими инструментами.

Это не так просто, как составляется банку и вызывая внутренние методы. Кажется, есть несколько трюков, чтобы сделать все это все работать. Вот пример простого файла 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...

И вот программа Java, которая называет -binomial Функция в 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 утверждение. Это, кажется, требуется, чтобы позволить вам получить доступ к функции CLOJURE что-то вроде статических методов в Java.

Во-вторых, чтобы создать функцию обертки, которая может быть вызвана Java. Обратите внимание, что вторая версия -binomial имеет черту перед ним.

И, конечно же, сама банка Clojure должна быть на классовом пути. Этот пример использовал банку Clojure-1.1.0.

Обновлять: Этот ответ был перезаряжен, используя следующие инструменты:

  • Clojure 1.5.1
  • Ленинден 2.1.3
  • JDK 1.7.0 Обновление 25

Часть Clojure

Сначала создайте проект и связанную структуру каталогов с помощью Leinhen:

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

Вы можете увидеть сообщение о загрузке банки Clojure в этот момент.

Теперь отредактируйте файл Clojure C:\projects\com.domain.tiny\src\com\domain\tiny.clj Такое, что он содержит программу Clojure, показанную в исходном ответе. (Этот файл был создан, когда Leinhen создал проект.)

Большая часть магии здесь находится в декларации пространства имен. То :gen-class говорит системе создать класс по имени com.domain.tiny с одним статическим методом называется binomial, функция, принимающая два целочисленных аргумента и возвращая двойной. Есть две аналогичные именованные функции binomial, традиционная функция Clojure, а также -binomial и обертка доступна из Java. Обратите внимание на дефис в имени функции -binomial. Отказ Префикс по умолчанию - это дефис, но он может быть изменен на что-то еще, если это необходимо. То -main Функция просто делает пару вызовов в биномиальную функцию, чтобы гарантировать, что мы получаем правильные результаты. Для этого собрать класс и запустите программу.

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

Вы должны увидеть вывод, показанный в исходном ответе.

Теперь упаковывайте его в банке и поставьте его удобным. Скопируйте банку Clojure тоже.

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 Part.

У Лейнингена есть встроенная задача, lein-javac, что должно быть в состоянии помочь с компиляцией Java. К сожалению, кажется, это сломан в версии 2.1.3. Он не может найти установленный JDK, и он не может найти репозиторий Maven. Пути для обоих встроенных пространств в моей системе. Я предполагаю, что это проблема. Любой Java IDE может справиться с компиляцией и упаковкой. Но для этого поста мы собираемся в школу и делаем ее в командной строке.

Сначала создайте файл Main.java С содержимым, показанным в оригинальном ответе.

Скомпилировать Java Part

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

Теперь создайте файл с какой-то метаинформацией, чтобы добавить в банку, которую мы хотим построить. В Manifest.txt, Добавьте следующий текст

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

Теперь упакуйте все это в один большой файл JAR, включая нашу программу Clojure и JAR CLOJURE.

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, но результат был преобразован в двойную Java.

Как уже упоминалось, IDE Java, вероятно, позаботится о грязных аргументах компиляции и упаковки.

Другие советы

С помощью Clojure 1.6.0 есть новый предпочтительный способ загрузить и вызывать функции Clojure. Этот метод теперь предпочтительнее вызовов RT напрямую (и заменяет многие другие ответы здесь). Javadoc есть здесь - главная точка входа clojure.java.api.Clojure.

Чтобы посмотреть и назвать функцию Clojure:

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

Функции внутри clojure.core автоматически загружаются. Другие пространства имен могут быть загружены по требованию:

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

IFnS может быть передан в функции высшего порядка, например, пример ниже пропуска plus к read:

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

Большинство IFnS в Cljure см. Функции. Несколько, однако, обращайтесь к нефункциональным значениям данных. Чтобы получить доступ к этому, используйте 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 году и работал в то время. См. Ответ Алекса Миллера для более современного решения.

Какой код звонит от 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);
  }
}

РЕДАКТИРОВАТЬ: Я написал этот ответ почти три года назад. В Clojure 1.6 существует собственный API именно с целью вызова Clojure из Java. Пожалуйста Ответ Алекса Миллера для актуальной информации.

Оригинальный ответ с 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)

А в Яве:

// 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 Code находятся в классе.

Я согласен с ответом Клатака, но я чувствовал, что новички также могут использовать:

  • пошаговая информация о том, как на самом деле получить этот бег
  • Информация, которая нынешняя для Clojure 1.3 и недавних версий Лейнингена.
  • Банка Clojure, которая также включает в себя основную функцию, так что его можно запустить автономный или связаны как библиотека.

Поэтому я охватил все это в этот блог пост.

Код Clojure выглядит так:

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

Настройка проекта Leinhen 1.7.1 выглядит так:

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

Код Java выглядит так:

import ThingOne.*;

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

Или вы также можете получить весь код из Этот проект на Github.

Это работает с 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)"));
    }
}

Если корпус использования должен включить банку, построенную с CLOJURE в приложении Java, я обнаружил, что имел отдельное пространство имен для интерфейса между двумя мирами, которые должны быть полезными:

(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, заключается в объявлении интерфейса для функций, которые вы хотите позвонить, а затем использовать функцию «Proxy» для создания экземпляра, которая им назначает их.

Вы также можете использовать компиляцию AOT для создания файлов классов, представляющих код вашего Clojure. Прочитайте документацию о компиляции, классу GUN-класса и друзья в документах API Clojure для получения подробной информации о том, как это сделать, но по сути вы создадите класс, который вызывает функции Clojure для каждого вызова метода.

Другая альтернатива - использовать новую функциональность Defprotocol и Deftype, которая также потребует компиляции AOT, но обеспечивает лучшую производительность. Я не знаю детали того, как это сделать еще, но вопрос в списке рассылки, вероятно, будет делать трюк.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top