JRubyからJavaのクラスを呼び出す方法

JRubyのパッケージ名に関して嵌っていたと昨日のエントリーで書きましたが、幾つかのトップレベルパッケージとそれ以外のパッケージで使用できる書式が違うという事が原因でした。サンプルなどでは、Swingやjava.lang.Systemなどがほとんどなのでミスリードされていたようです。
結論からいえば、JavaのライブラリをJRubyから実行する場合、次のように記述する方法が良いと思われます。

require 'java'
require 'path/to/jar_file'
module JavaBar
  include_package "foo.bar"
end
# Javaのfoo.bar.Fooクラスのコンストラクタを呼び出す
JavaBar::Foo.new()

一方、java.lang.Systemを使う場合は、次のように書くことが出来ます。

require 'java'
java.lang.System.println('Hello JRuby')

ですが、次のように書くとNameErrorとなります。

require 'java'
require 'path/to/jar_file'
foo.bar.Foo.new()

以下、Accessing and Importing Java Classesに書かれている内容を簡単にまとめます。

JRubyからJavaを使う

JRubyからJavaのライブラリを使用する場合、require 'java'の宣言を行います。

require 'java'

ライブラリを使用する

Javaで言うところのクラスパスを通す方法ですが、2つの方法があります。
1つ目の方法は、JRubyのクラスパスに拡張ライブラリ(Jar)のパスを指定することです。これはTomcatのクラスパスにJarを置くようなものですから、汚染範囲が広くクラスのコンフリクトなどの問題に悩まされるかもしれません。
2つ目の方法は、requireを使用することで動的に通す方法です。

require 'path/to/mycode.jar'

尚、Netbeansの場合、プロジェクトのプロパティのJavaからJRubyのクラスパスを指定できるので、簡単に試してみるならば良い方法かと思われます。

Javaのクラス名を解決する

Javaのクラスはパッケージ名+クラスという書式で表され、パッケージ名はドット区切りになっています。これをRubyから参照する方法は大きく3通りあります。

Rubyのネストされたモジュールとして認識する

最初のパターンは、JavaのパッケージをJavaモジュールのネストモジュールとして認識させ、ネストされたパッケージモジュールに属するクラスというフォーマットを取るパターンです。例を見た方が早いのでみてみましょう。
Java: org.foo.department.Widget
Ruby: Java::OrgFooDepartment::Widget
すなわち、次のような形式になっています。

  • Javaのパッケージは、Javaモジュールに属する
  • パッケージ名はドットを省いたキャメルケースで表現する
Javaのパッケージ名をそのまま使用する

パッケージのトップレベルがjava, javax, org, comの場合のみ、パッケージ名をそのまま記述すればJavaのクラスとして認識されます。例えば、java.lang.SystemはJavaのクラスとして認識されますが、jp.deathmarch.Foo はNameErrorを発生させます。
このパターンはJRubyの様々なサンプルで紹介されているのでよく見かけるのですが、パッケージ名のトップが特定の名前でなければならないので注意して下さい。
なお、次のコードは、undefined method `lang' for "Hello Java!":String (NoMethodError) と例外が発生してしまいます。

require 'java'
java = 'Hello Java!'
java.lang.System.println(java)

このようにjavaやcomを変数として宣言したりすると、予期せぬ挙動になるので使用しない方がいいかもしれません。

Javaのパッケージ名をそのまま使用できるようにする

それでも、Javaのコードをそのまま貼り付けるなど、どうしてもJavaのパッケージ名をそのまま使用したい場合は次のように定義します。

def jp
 Java::Jp
end

この定義により、jp.deathmarch.FooはJavaのクラス名として認識されるようになります。これもJavaソースコードを取り込んで実行したいような状況でなければ避ける方がいいと思います。

import

次にimport文に関してJRubyでどのように扱うかについてまとめます。JRuby上では次のようにJava形式の書式かパケージ名を文字列として記述することができます。

import 'java.lang.System'
import java.io.File

ただし、import後はクラス名が上書きされてしまうため、Rubyに同名のクラスがあった場合に問題を引き起こします。

import java.io.File
newfile = File.new("file.txt")
File.open('README', 'r') {|f| puts f.readline }

この場合、File.openはjava.io.Fileに対する呼び出しとなる為、NoMethodErrorが発生します。
尚、import文時のパッケージ名の解決時も、java, javax, org, com で始まるパッケージ以外はNameErrorになるので注意して下さい*1

include_package

include_packageを使えば、パッケージ名をRubyのモジュール名に置き換えるような形でJavaのクラスを使用することができます。include_package が定義されたRubyモジュールは、Javaクラスのパッケージ名に対して名前空間の解決をサポートするでしょう。

require 'java'
module JavaLang
  include_package "java.lang"
end
JavaLang::System.out.println("Hello!")

とても長いパッケージ名であっても短縮した名前でモジュールとして使用できるようなる為、ネストしたモジュールの形式で書くよりも効率良く使用できるでしょう。

まとめ

  • Rubyのコード内にjavaのパッケージ書式で記述するのは避ける
  • Javaのクラスを呼び出す場合、モジュールを定義する方法が安全
  • Javaのソースファイルを取り込んでなにかをさせたいならば、安全を確保した上でimportなどを有効になるように細工する
  • まぜるな、危険
  • まざれば、自然

よいJRubyライフを!

*1:結局、ここにはまっていたわけです