자바에서 클로저 호출
"java에서 clojure 호출"에 대한 Google의 인기 히트작 대부분이 오래되었으며 clojure.lang.RT
소스 코드를 컴파일하는 데 사용 하는 것이 좋습니다 . Clojure 프로젝트에서 jar를 이미 빌드하고 클래스 경로에 포함했다고 가정하면 Java에서 Clojure를 호출하는 방법에 대한 명확한 설명을 도울 수 있습니까?
업데이트 :이 답변이 게시 된 이후 사용 가능한 도구 중 일부가 변경되었습니다. 원래 답변 후에 현재 도구로 예제를 작성하는 방법에 대한 정보가 포함 된 업데이트가 있습니다.
항아리를 컴파일하고 내부 메소드를 호출하는 것만 큼 간단하지 않습니다. 그래도 모두 작동하게 만드는 몇 가지 트릭이있는 것 같습니다. 다음은 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 함수에 액세스 할 수 있어야합니다.
두 번째는 Java에서 호출 할 수있는 랩퍼 함수를 작성하는 것입니다. 두 번째 버전 -binomial
앞에는 대시가 있습니다.
물론 Clojure jar 자체는 클래스 경로에 있어야합니다. 이 예제는 Clojure-1.1.0 jar을 사용했습니다.
업데이트 :이 답변은 다음 도구를 사용하여 다시 테스트되었습니다.
- 클로저 1.5.1
- 라이닝 겐 2.1.3
- JDK 1.7.0 업데이트 25
클로저 부분
먼저 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
이 시점에서 Clojure jar 다운로드에 대한 메시지가 표시 될 수 있습니다.
이제 C:\projects\com.domain.tiny\src\com\domain\tiny.clj
원래 답변에 표시된 Clojure 프로그램이 포함 되도록 Clojure 파일을 편집하십시오 . (이 파일은 Leiningen이 프로젝트를 만들 때 생성되었습니다.)
여기서 마술의 대부분은 네임 스페이스 선언에 있습니다. 이 :gen-class
시스템은 두 개의 정수 인수를 취하고 double을 리턴하는 함수 인 com.domain.tiny
이라는 단일 정적 메소드로 이름 지정된 클래스를 작성하도록 시스템에 지시합니다 binomial
. 이와 유사한 두 개의 함수 binomial
, 전통적인 Clojure 함수 및 -binomial
Java에서 액세스 할 수있는 랩퍼가 있습니다. 함수 이름의 하이픈에 유의하십시오 -binomial
. 기본 접두사는 하이픈이지만 원하는 경우 다른 것으로 변경할 수 있습니다. 이 -main
함수는 이항 함수를 몇 번 호출하여 올바른 결과를 얻도록합니다. 그렇게하려면 클래스를 컴파일하고 프로그램을 실행하십시오.
C:\projects\com.domain.tiny>lein run
원래 답변에 출력이 표시되어야합니다.
이제 항아리에 포장하고 편리한 곳에 두십시오. Clojure jar도 복사하십시오.
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.
자바 부분
Leiningen에는 lein-javac
Java 컴파일에 도움 이되는 기본 제공 태스크 가 있습니다. 불행히도 버전 2.1.3에서는 깨진 것 같습니다. 설치된 JDK를 찾을 수없고 Maven 저장소를 찾을 수 없습니다. 두 경로 모두 내 시스템에 공백이 있습니다. 나는 그것이 문제라고 생각합니다. 모든 Java IDE는 컴파일 및 패키징도 처리 할 수 있습니다. 그러나이 포스트의 경우, 우리는 구식 학교를 다니고 있으며 명령 줄에서하고 있습니다.
먼저 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 프로그램과 Clojure jar를 포함하여 하나의 큰 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 단독으로 생성 된 것과 동일하지만 결과는 Java double로 변환되었습니다.
언급 한 바와 같이, Java IDE는 복잡한 컴파일 인수 및 패키징을 처리합니다.
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]"));
Most IFn
s in Clojure refer to functions. A few, however, refer to non-function data values. To access these, use deref
instead of fn
:
IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);
Sometimes (if using some other part of the Clojure runtime), you may need to ensure that the Clojure runtime is properly initialized - calling a method on the Clojure class is sufficient for this purpose. If you do not need to call a method on Clojure, then simply causing the class to load is sufficient (in the past there has been a similar recommendation to load the RT class; this is now preferred):
Class.forName("clojure.java.api.Clojure")
EDIT This answer was written in 2010, and worked at that time. See Alex Miller's answer for more modern solution.
What kind of code are calling from Java? If you have class generated with gen-class, then simply call it. If you want to call function from script, then look to following example.
If you want to evaluate code from string, inside Java, then you can use following code:
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: I wrote this answer almost three years ago. In Clojure 1.6 there is a proper API exactly for the purpose of calling Clojure from Java. Please Alex Miller's answer for up to date information.
Original answer from 2011:
As I see it, the simplest way (if you don't generate a class with AOT compilation) is to use clojure.lang.RT to access functions in clojure. With it you can mimic what you would have done in Clojure (no need to compile things in special ways):
;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure
(require 'foo.ns)
(foo.ns/bar-fn 1 2 3)
And 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);
It is a bit more verbose in Java, but I hope it's clear that the pieces of code are equivalent.
This should work as long as Clojure and the source files (or compiled files) of your Clojure code is on the classpath.
I agree with clartaq's answer, but I felt that beginners could also use:
- step-by-step information on how to actually get this running
- information that's current for Clojure 1.3 and recent versions of leiningen.
- a Clojure jar that also includes a main function, so it can be run standalone or linked as a library.
So I covered all that in this blog post.
The Clojure code looks like this:
(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." ))
The leiningen 1.7.1 project setup looks like this:
(defproject ThingOne "1.0.0-SNAPSHOT"
:description "Hello, Clojure"
:dependencies [[org.clojure/clojure "1.3.0"]]
:aot [ThingOne.core]
:main ThingOne.core)
The Java code looks like this:
import ThingOne.*;
class HelloJava {
public static void main(String[] args) {
System.out.println("Hello from Java!");
core.foo (12345);
}
}
Or you can also get all the code from this project on github.
This works with 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)"));
}
}
If the use case is to include a JAR built with Clojure in a Java application, I have found having a separate namespace for the interface between the two worlds to be beneficial:
(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)))
The core namespace can use the injected instance to accomplish its tasks:
(ns example-app.core)
(defn send-mails
[{:keys [weather-forecast]}]
(let [temp (.getTemperature weather-forecast)] ...))
For testing purposes, the interface can be stubbed:
(example-app.core/send-mails
(reify com.example.WeatherForecast (getTemperature [this] ...)))
Other technique that works also with other languages on top of JVM is to declare an interface for functions you want to call and then use 'proxy' function to create instance that implemennts them.
You can also use AOT compilation to create class files representing your clojure code. Read the documentation about compilation, gen-class and friends in the Clojure API docs for the details about how to do this, but in essence you will create a class that calls clojure functions for each method invocation.
Another alternative is to use the new defprotocol and deftype functionality, which will also require AOT compilation but provide better performance. I don't know the details of how to do this yet, but a question on the mailing list would probably do the trick.
참고URL : https://stackoverflow.com/questions/2181774/calling-clojure-from-java
'IT story' 카테고리의 다른 글
16 진의 printf () 형식화 (0) | 2020.06.02 |
---|---|
StringBuffer / StringBuilder와 동등한 C ++? (0) | 2020.06.02 |
Xamarin.Form의 LayoutOptions, 특히 채우기 및 확장의 차이점은 무엇입니까? (0) | 2020.06.02 |
Android : ScrollView 내부의보기가 보이는지 확인하는 방법은 무엇입니까? (0) | 2020.06.02 |
테스트 드라이버에서 직접 사용자 지정 Django manage.py 명령을 호출하려면 어떻게해야합니까? (0) | 2020.06.02 |