Tips for accessing Java objects from JRuby

先日、JRubyの最新バージョン1.1の正式版がリリースされました。JRuby 1.1の目玉はjrubycコマンドによるAOT(Ahead Of Time)コンパイルが利用できるようになったことですが、それ以外にも本家MRI(Matz Ruby Implementation)に対する互換性の改善やRubyランタイムからJavaオブジェクトへアクセス方法の改善が非常に細かいレベルで行われています。

このエントリでは、純粋なRubyとしてJRubyを使う場合ではなく、RubyからJavaクラスAPIを使用する場合のTIPSをいくつか紹介します。

JavaプログラマのツールとしてのJRuby

純粋にJavaだけを使用した開発プロジェクトに参加しているプログラマや設計者にとっても、JRubyは簡単な動作検証のための道具としてとても役に立ちます。プログラマは時々既存のJavaクラスライブラリのAPI仕様書からは読み取れない動作仕様を確認するため、プロダクション用のソースコードを書く前に(あるいは書いている途中で)、これらのAPIを呼び出す小さなテストプログラムを書いて、実際の振る舞いを確認することがあります。

このような場合、JRubyのjirbコマンドを使って、目的のJavaクラスオブジェクトをJRubyランタイム上に生成し、インタラクティブに目的のメソッドを呼び出して実行結果を確認したり、オブジェクトの状態を簡単に確認することができます。

もちろん、このような目的であればJRuby以外のスクリプト言語でも同様の目的を果たすことができます。例えば、GroovyははじめからJavaランタイムを前提としたスクリプト言語であるため、Javaのシンタックスに近い形で、スクリプトからJavaのクラスオブジェクトにアクセスできます。どのスクリプト言語を選択するかは好みによるところが大きいと思いますが、JRubyにはGroovyにはない柔軟性があるため、個人的にはJRubyを好んで使用しています。その一方で、JRubyからJavaクラスAPIへのアクセスにはちょっとしたコツが必要になります。以下では、JRuby初心者が分かりにくいと思われるJavaクラスAPIアクセスに関するTIPSをいくつか紹介します。

Rubyらしいメソッド名を使う

Javaクラスのメソッド名はtoOctetString()のようにinterCap形式で定義するのが一般的ですが、Rubyの文化では単語の区切りにアンダースコア(_)を使う方が好まれているようです。JRubyでは全てのinterCap形式のメソッド名に対応してアンダースコアで区切られたメソッド名をランタイムに追加しています。例えば、java.lang.Integerクラスに定義されているtoXXXString()はto_xxx_string()としてもアクセスできるようになっています。

>> integer = java.lang.Integer.new 0
>> integer.methods.each {|m| p m if /to.\*[Ss]tring/ =~ m};''
"toBinaryString"
"toString"
"toOctalString"
"to_octal_string"
"to_string"
"toHexString"
"to_hex_string"
"to_binary_string"

>> java.lang.Integer.to_hex_string 255
=> "ff"

また、ビーンプロパティのアクセッサメソッドも、Rubyの文化に合わせgetterにはプリフィックス"get"をつけないものが追加され、setterの代わりに演算子"="を使用した代入文のためのメソッドも追加されています。

>> button = javax.swing.JButton.new
>> button.methods.each {|m| p m if /.\*[Ll]abel/ =~ m};''
"getLabel"
"setLabel"
"label"
"label="
"get_label"
"set_label"

>> button.label = 'Press Me!'
=> "Press Me!"

さらにbooleanタイプのプロパティの場合は、"is<プロパティ名>"に対して"<プロパティ名>?"という形式のメソッドも追加されます。

>> button = javax.swing.JButton.new
>> button.methods.each {|m| p m if /.\*[Oo]paque.\*/ =~ m};''
"set_opaque"
"opaque="
"opaque?"
"is_opaque"
"isOpaque"
"opaque"
"setOpaque"

>> button.opaque?
=> true

interCap形式のメソッドを使うか、Rubyらしいメソッドを使うかは好みの問題だと思いますので、どちらかお好きな方を使用してください。このように同じことをやるのに、たくさんの表現形式を用意してしまうところなどをみると、JRuby実装は本当にRubyらしさを追求しているなと思います。

staticメンバへのアクセス

これはJavaクラスのアクセスに特化した話ではないのですが、Rubyの言語仕様に合わせて、クラスのstaticなメンバ変数(定数)にアクセスする場合には、"::"を使用します。

>> javax.swing.JSlider::VERTICAL        
=> 1

# インタフェースクラスから直接参照することも可能

>> javax.swing.SwingConstants::HORIZONTAL
=> 0 

staticメソッドにアクセスする場合は、"::"または"."のどちらかを使用します。staticなメンバ変数のアクセスに"."は使用できません。

>> java.util.Calendar::getInstance()
=> #<Java::JavaUtil::GregorianCalendar:0x39826 @java_object=...

# または

>> java.util.Calendar.getInstance()
=> #<Java::JavaUtil::GregorianCalendar:0xc7ecd5 @java_object=...

Ruby配列をJavaの配列に変換する

Ruby Stringや数値(Fixnum、Float)、HashなどのオブジェクトがJava側のメソッドの引数に与えられた場合は適切なJavaのオブジェクトに自動変換されますが、Ruby配列はJavaの配列に自動的には変換されません。例えば、Ruby配列を引数にjava.util.Arrays.asList(Object[])メソッドを実行すると以下のようにタイプエラーとなります。

>> array = ["One", "Two", "Three", "Four"]
=> ["One", "Two", "Three", "Four"]
>> java.util.Arrays.as_list array
TypeError: expected [[Ljava.lang.Object;]; got: [$Proxy8]; error: argument type mismatch

Ruby配列をJavaの配列に変換するには、以下のようにto_java()メソッドを用います。

>> java.util.Arrays.as_list array.to_java
=> #<#<Class:01x12e7234>:0x4bd767 @java_object=[One, Two, Three, Four]>

to_java()メソッドには変換先Java配列のデータタイプを明示することもできます。引数を省略した場合は、Object[]型に変換されます。

>> [1,2,3].to_java
=> #<#<Class:01xc5575>:0x1be8bf1 @java_object=[Ljava.lang.Object;@d591a6>
>> [1,2,3].to_java :byte
=> #<#<Class:01x13244cd>:0x14300c8 @java_object=[B@1e867d6>
>> [1,2,3].to_java :int
=> #<#<Class:01x186dd93>:0x13d0fea @java_object=[I@1dffb78>
>> [1,2,3].to_java :string
=> #<#<Class:01xa6faa9>:0x928739 @java_object=[Ljava.lang.String;@1ebe8ec>

引数に指定できるデータタイプは、プリミティブ型とそのラッパークラス、String、BigDecimalが指定できるようです。指定可能な引数のリストはドキュメントに明示されていないようなので、JRubyに含まれる以下のソースを確認してみてください。

なお、to_java()メソッドは多次元配列に対しても有効です。例えば、2次元配列をモデルの初期データとしてSwingのJTableオブジェクトを生成するために、コンストラクタjavax.swing.JTable#JTable(Object[][], Object[])を呼び出す場合は以下のようなスクリプトになります。

>> table = javax.swing.JTable.new(
?> [['Thomas', 'Enebo'], ['Charles', 'Nutter']].to_java,
?> ['First name', 'Last name'].to_java)
=> #<Java::JavaxSwing::JTable:0x53b2c @java_object=...

Javaの配列をRuby配列に変換する

Javaの配列は配列要素を分かりやすく表示する専用のtoString()メソッドが用意されていないため、Javaのメソッドが返したJava配列の中身がそのままではよく分かりません。

>> javalist = java.util.Arrays.as_list array.to_java
=> #<#<Class:01x12e7234>:0x13577ca @java_object=[One, Two, Three, Four]>
>> javalist.to_array
=> #<#<Class:01xc5575>:0x1f854bd @java_object=[Ljava.lang.Object;@1f80c0e>

Javaの配列をRuby配列に変換するには、to_ary()メソッドを使用します。

>> javalist.to_array.to_ary
=> ["One", "Two", "Three", "Four"]

まだ、他にもありますが今日のところはここまでにしておきます。あまり、JRuby 1.1での拡張部分にふれることが出来なかったため、続きは次回のエントリで書きます。

投稿されたコメント:

cool

Posted by wow gold on 11月月 03日, 2008年 at 02:42 午前 JST #

コメント
  • HTML文法 不許可
About

Takashi Nishigaya
Principal Consultant
Technology Solution Consulting
Oracle Consulting Services

Search

Categories
Archives
« 4月 2014
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
今日