Статьи

Clojure & Java Interop

Около года назад мне позвонили и спросили, хочу ли я присоединиться к другой команде в DRW . Команда поддерживает (прежде всего) Java-приложение, но требования к производительности также позволят писать его на языке более высокого уровня. В тот момент я писал Clojure (в основном) полный рабочий день, поэтому мой ответ был прост: я бы хотел присоединиться, но я хочу заняться будущей разработкой с использованием Clojure.

Год спустя у нас все еще много Java, но подавляющее большинство нового кода, который я добавляю, — это Clojure. Одна из главных причин, по которой я могу так свободно использовать Clojure, — бесшовное взаимодействие с Java.

Выполнить Clojure из Java
Вызов Clojure из Java так же прост, как загрузка файла .clj и вызов метода из этого файла. Я использовал тот же пример лет назад, но я приведу это здесь для простоты.

; interop/core.clj
(ns interop.core)

(defn print-string [arg]
  (println arg))

// Java calling code
RT.loadResourceScript("interop/core.clj");
RT.var("interop.core", "print-string").invoke("hello world");

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

lein jar && java -cp "interop-1.0.0.jar:lib/*" interop.Example

Выполнение Java из Clojure

На этом этапе Java выполняет некоторый код Clojure, а также Clojure с использованием объекта, созданного в Java. Несмотря на то, что мы находимся в Clojure, мы можем легко вызывать методы для любого объекта Java.

(ns interop.core)

(defn print-string [arg]
  (println arg "is" (.length arg) "characters long"))

commit

Приведенный выше код (с использованием метода length экземпляра String) создает следующий вывод.

hello world is 11 characters long

Вызов метода Java и передача дополнительных аргументов также просты в Clojure.

(ns interop.core)

(defn print-string [arg]
  (println (.replace arg "hello" "goodbye")))

commit

Приведенный выше код производит следующий вывод.

goodbye world

Есть несколько других вещей, которые нужно знать о вызове Java из Clojure. В следующих примерах показано, как вызывать статические методы, использовать перечисления и использовать внутренние классы.

(ns interop.core)

(defn print-string [arg]
  ;;; calling a static method
  (println (String/valueOf true))

  ;;; using an enum
  (println (java.util.concurrent.TimeUnit/SECONDS))

  ;;; using a Java nested (inner) class. Note, in Clojure you
  ;;; use a $ instead of a .
  (println (java.util.AbstractMap$SimpleEntry. "key" "val")))

коммит

А, вывод:

true
#< SECONDS>
#<SimpleEntry key=val>

Создание объектов Java в Clojure

При работе с Clojure вам, вероятно, захочется взаимодействовать с существующими объектами Java, но, возможно, вам также понадобится создавать новые экземпляры объектов Java. Возможно, вы заметили точку в конце Abstract $ SimpleEntry. в предыдущем примере — это то, как вы инструктируете Clojure для создания экземпляра объекта Java. В следующем примере показана точечная нотация для вызова конструктора класса String.

(ns interop.core)

(defn print-string [arg]
  (println (String. arg)))

commit

В этот момент наш вывод возвращается к исходному выводу.

hello world

При создании объектов Java часто полезно знать, какие интерфейсы Java реализуют структуры данных Clojure. В следующих примерах демонстрируется, как можно создавать объекты Java при передаче структур данных (и функций) Clojure в качестве аргументов конструктора.

(ns interop.core)

(defn print-string [arg]
  ;;; pass a Clojure vector where Java expects a java.util.Collection
  (println (java.util.HashSet. ["1" "2"]))

  ;;; pass a Clojure map where Java expects a java.util.Map
  (println (java.util.LinkedHashMap. {1 "1" 2 "2"}))

  ;;; pass a Clojure function where Java expects a Runnable
  (println (Thread. (fn [] (println "clojure fns are runnables (and callables)")))))

commit

Выходные данные показывают построенные объекты Java.

#<HashSet [2, 1]>
#<LinkedHashMap {1=1, 2=2}>
#<Thread Thread[Thread-1,5,main]>

Вызов конструкторов в Clojure очень прост, но это не всегда вариант при создании объекта Java. Иногда вам, вероятно, потребуется создать экземпляр интерфейса Java. Clojure предоставляет как
прокси, так и
reify для создания экземпляров интерфейсов Java. В следующем примере демонстрируется синтаксис использования прокси или reify.

(ns interop.core)

(defn proxy-coll []
  (proxy [java.util.Collection] []
    (add [o]
         (println o)
         true)))

(defn reify-coll []
  (reify java.util.Collection
    (add [this o]
         (println o)
         (println this)
         true)))

(defn main []
  (.add (proxy-coll) "this string is printed on proxied.add")
  (.add (reify-coll) "this string is printed on reified.add"))

совершить

примечание, я также изменил Example.java (подробности доступны в вышеуказанном связаны фиксации). Синтаксис прокси и reify довольно похожи, и оба предлагают дополнительные опции, которые стоит изучить. Основные различия между этими двумя простыми примерами:

  • Реализация прокси требует пустого вектора, в котором мы могли бы указать аргументы конструктора (если бы это был абстрактный класс вместо интерфейса).
  • Список аргументов для всех методов reify будет указывать экземпляр reified в качестве первого аргумента. В нашем примере метод Collection.add принимает только один аргумент, но в нашем примере мы также получаем экземпляр коллекции.

Возможно, вы также заметили, что обе реализации add в конце имеют «true» — в нашем примере мы жестко программируем возвращаемое значение add, чтобы всегда возвращать true. Следующий вывод является результатом выполнения текущего примера кода.

this string is printed on proxied.add
this string is printed on reified.add
#<core$reify_coll$reify__11 interop.core$reify_coll$reify__11@556917ee>

Стоит прочитать документы, чтобы определить, хотите ли вы прокси или reify; Однако, если вы не видите четкого выбора, я бы выбрал Рейфи.

Возврат объектов из Clojure в Java

Наш текущий Example.java возвращает что-то из вызова для вызова в clojure.lang.Var, которое возвращается из RT.var («interop.core», «main»), но мы игнорируем это поэтому мы понятия не имеем, что вернулось. * Давайте изменим код и вернем что-то намеренно.

// interop/Example.java
package interop;

import clojure.lang.RT;

public class Example {
    public static void main(String[] args) throws Exception {
        RT.loadResourceScript("interop/core.clj");
        System.out.println(RT.var("interop.core", "main").invoke());
    }
}

; interop/core.clj
(ns interop.core)

(defn main []
  {:a "1" :b "2"})

Запуск наших изменений приводит к следующему выводу.

{:a "1", :b "2"}

commit

В этот момент мы вернулись на землю Java после короткой поездки в Clojure, чтобы получить значение. Возврат большинства объектов будет довольно простым; однако в какой-то момент вы можете захотеть вернуть функцию Clojure. Это также оказывается довольно простым, поскольку функции Clojure являются экземплярами интерфейса IFn. Следующий код демонстрирует, как вернуть функцию Clojure и вызвать ее из Java.

// interop/Example.java
package interop;

import clojure.lang.RT;

public class Example {
    public static void main(String[] args) throws Exception {
        RT.loadResourceScript("interop/core.clj");
        clojure.lang.IFn f = (clojure.lang.IFn) RT.var("interop.core", "main").invoke();
        f.invoke("hello world");
    }
}

// interop/core.clj
(ns interop.core)

(defn main [] println)

commit

Приведенный выше пример возвращает функцию println из interop.core / main, а затем вызывает функцию println из Java. Я выбрал только один аргумент для вызова; однако метод IFn.invoke имеет различные переопределения, позволяющие передавать несколько аргументов. Приведенный выше код работает, но его можно упростить до следующего примера.

package interop;

import clojure.lang.RT;

public class Example {
    public static void main(String[] args) throws Exception {
        clojure.lang.IFn f = (clojure.lang.IFn) RT.var("clojure.core", "println");
        f.invoke("hello world");
    }
}

commit

Кажется подходящим завершением, что наш конечный результат совпадает с нашим исходным результатом.

hello world

* на самом деле, это последняя вещь, которая возвращается, или «правда» для этого конкретного случая.

 

С http://blog.jayfields.com/2011/12/clojure-java-interop.html