CLOVER🍀

That was when it all began.

ClojureでJavaのクラスを作る

これまで、だいぶ目を背けていたのですが、ClojureとJavaを使うにあたり、そろそろClojureでJavaのクラスを作成したり継承したりというのを覚えてみようと思います。

う〜ん、遅いネタですねぇ…。

ま、気にせずいってみましょう。

まずは

Clojureでソースを書きます。サンプルとしては、こんな感じ。
foo.clj

(ns foo
  (:gen-class
   :methods [#^{:static true} [bar [String] String]
             #^{:static true}[fuga [] void]
             [echo [String] String]]))

(defn -main [] (println "Hello World Main Method"))
(defn -bar [name] (str "Hello World By " name " !!"))
(defn -fuga [] (println "Hello World"))

(defn -echo [this param] param)

超適当。パッケージもなし。ちなみに、パッケージを切る場合は

(ns foo.bar)

みたいにして、なおかつfoo/bar.cljみたいな配置にしないとダメみたいですよ。

で、このクラスは以下のようになっています。

  • クラス名はfooで、Javaから呼べる
  • public static void mainメソッドを持つ
  • staticメソッド、barとfugaを持つ
  • インスタンスメソッドechoを持つ

Javaのクラスを生成するのは、

  (:gen-class

で指示しています。メソッドについては、

   :methods [#^{:static true} [bar [String] String]
             #^{:static true}[fuga [] void]
             [echo [String] String]]))

で指示。staticメソッドは、メタデータで指示しています。:methodsキーワードの指定方法は、ClojureDocsとかを見ればよいと思いますが

:methods [ [name [param-types] return-type], ...]

という書き方をします。voidの場合は、voidと書けばよいような…。

続いて、メソッドの宣言。

(defn -main [] (println "Hello World Main Method"))
(defn -bar [name] (str "Hello World By " name " !!"))
(defn -fuga [] (println "Hello World"))

(defn -echo [this param] param)

先頭に「-」が付いていますが、これはgen-classリーダマクロのprefixキーワードのデフォルト値が「-」だからです。
また、インスタンスメソッドの場合は第1引数にthisになるものが入ります。Pythonと同じようなもの?

コンパイル

コンパイルを行うには、compile関数を使います。では、やってみましょう。

$ clj
Clojure 1.4.0
user=> (compile 'foo)
CompilerException java.io.IOException: そのようなファイルやディレクトリはありません, compiling:(foo.clj:1) 

コケましたね。これはどういうことかというと…compile関数の説明を読んでみます。

Compiles the namespace named by the symbol lib into a set of
classfiles. The source for the lib must be in a proper
classpath-relative directory. The output files will go into the
directory specified by *compile-path*, and that directory too must
be in the classpath.

というわけで、*compile-path*に対応するディレクトリが必要だよ、と。*compile-path*の値を確認すると

user=> (println *compile-path*)
classes
nil

なので、1度REPLを抜けてディレクトリを作成します。

$ mkdir classes

再度、トライ。

$ clj
Clojure 1.4.0
user=> (compile 'foo)   
foo

今度は、うまくいきました。結果は、こんな感じ。

$ ls -l classes
合計 28
-rw-rw-r-- 1 xxxxx xxxxx  929 Oct 19 11:14 foo$_bar.class
-rw-rw-r-- 1 xxxxx xxxxx  544 Oct 19 11:14 foo$_echo.class
-rw-rw-r-- 1 xxxxx xxxxx  822 Oct 19 11:14 foo$_fuga.class
-rw-rw-r-- 1 xxxxx xxxxx  834 Oct 19 11:14 foo$_main.class
-rw-rw-r-- 1 xxxxx xxxxx 1469 Oct 19 11:14 foo$loading__4784__auto__.class
-rw-rw-r-- 1 xxxxx xxxxx 2277 Oct 19 11:14 foo.class
-rw-rw-r-- 1 xxxxx xxxxx 3270 Oct 19 11:14 foo__init.class

mainメソッドを持っているので、直接Javaから起動できます。

$ java -cp classes:/usr/local/clojure/clojure.jar foo
Hello World Main Method

Javaから呼んでみる

では、作成したfooクラスをJavaから呼び出してみます。
Caller.java

public class Caller {
    public static void main(String[] args) {
        System.out.println(foo.bar("Clojure"));
        foo.fuga();

        foo f = new foo();
        System.out.println(f.echo("foo"));
    }
}

コンパイルして、実行。

$ javac -d classes -cp classes Caller.java
$ java -cp classes:/usr/local/clojure/clojure.jar Caller
Hello World By Clojure !!
Hello World
foo

OKですね!でも、けっこうハマりました…。