8. JRuby から Java へのアクセス
ここでは jruby 1.1.6 に固有の特徴を説明する。 その目的は,君が jruby を Java のクラスを利用するための簡易な言語として使えるようにすることである。
8.1 文字エンコーディング
jruby 1.1.6 は,ruby 1.8.6 と同じく文字を UTF-8/EUC-JP/SJIS 扱いするように指定できる。 無指定時は 1 バイトずつの列として扱う。
p /^あ*$/ === "ああ"
を内容とするファイルを作成して実験してみよう。 これはマルチバイト文字「あ」を1文字の「あ」として認識しないと true にならない。
$ cat a.rb p /^あ*$/ === "ああ" $ nkf -w a.rb | jruby false $ nkf -w a.rb | jruby -Ku true $ nkf -e a.rb | jruby -Ke true $ nkf -s a.rb | jruby -Ks true $ nkf -s a.rb | jruby -Ke false […以下略…]
文字エンコーディングが一致するとき,正しく認識されていることが分かる。
p "あ"
を内容とするファイルを作成して,マルチバイト文字「あ」が 出力時に1文字の「あ」として認識されるか実験してみよう。
$ cat b.rb p "あ" $ nkf -w b.rb | jruby | nkf "\343\201\202" $ nkf -w b.rb | jruby -Ku | nkf "あ" $ nkf -e b.rb | jruby -Ke | nkf "あ" $ nkf -s b.rb | jruby -Ks | nkf "あ" $ nkf -s b.rb | jruby -Ke | nkf "\202\240" […以下略…]
文字エンコーディングが一致するとき,正しく認識されていることが分かる。
また,無指定時を含めいずれの場合でも,単純な文字列入出力 (puts や gets など) では,文字列をバイト透過に扱うから,バイナリ・データとして漢字や かな を扱うことができる。
$ cat c.rb puts "あ" $ jruby c.rb あ $ jruby -Ku c.rb あ $ jruby -Ke c.rb あ $ jruby -Ks c.rb あ
8.1.1 制限事項
現在の制限事項として,1 章 注釈で述べたように, 文字エンコーディングの指定にかかわらず,第2バイトが 「\」 になるいわゆる SJIS ダメ文字は,ruby と異なり,正しく字句解析できない。
$ nkf -S sososo.rb puts "ソソソ" $ ruby sososo.rb | nkf sososo.rb:1: unterminated string meets end of file $ ruby -Ks sososo.rb | nkf ソソソ $ jruby -Ks sososo.rb | nkf :1: sososo.rb:2: unterminated string meets end of file (SyntaxError)
マルチバイト文字のバイト表現と ASCII 文字のバイト表現が重ならない EUC-JP や UTF-8 には,この問題はない。
また,文字エンコーディングの指定にかかわらず, Java との相互作用では,jruby の文字列はつねに UTF-8 として解釈される。 下記に示すように,Java にバイト列として例えば 0xE3 0x81 0x82 を渡すと,それが「あ」の文字コードとして解釈される。
$ cat d.rb
include Java
java.lang.System.out.println("あ")
$ nkf -w d.rb | jruby | nkf
あ
$ nkf -w d.rb | jruby -Ku | nkf
あ
$ nkf -w d.rb | jruby -Ke | nkf
あ
$ nkf -e d.rb | jruby -Ke | nkf
??
java.lang.System.out.println
メソッドは,Unicode の「あ」をプラットフォームのデフォルト文字エンコーディング
(例えば SJIS) で印字します。
以上のような制限事項から,今のところ jruby のプログラムは UTF-8 で記述するのが最も妥当である。 そうすることは現在の技術的トレンドとも合致している。 -Ku オプションを指定すれば,正規表現に日本語文字を使っても問題ない。
8.2 標準 Java ライブラリへのアクセス
下記を行うことによって標準 Java ライブラリへのアクセスが可能になる (参照: "JRuby Cookbook", November 2008, §1.5)。
include Java
あるいは include するかわりに,こうしてもよい
(参照: Calling Java from JRuby - JRubyWiki)。
require 'java'
UTF-8 で下記のスクリプトを作成し,jruby で実行すると,
include Java frame = javax.swing.JFrame.new("窓の例") label = javax.swing.JLabel.new("こんにちは") frame.getContentPane.add(label) frame.setDefaultCloseOperation(javax.swing.JFrame::EXIT_ON_CLOSE) frame.pack frame.setVisible(true)
このようなウィンドウが表示される:
ここで javax.swing.JFrame という表現がなにか Ruby 言語の範囲外の魔法に見えるかもしれないが, 実は完全に正当な Ruby 式である。上記をメソッド呼出しの丸括弧を省略しないで書くとこうなる。
include(Java) frame = javax().swing().JFrame().new("窓の例") label = javax().swing().JLabel().new("こんにちは") frame.getContentPane().add(label) frame.setDefaultCloseOperation(javax().swing().JFrame()::EXIT_ON_CLOSE) frame.pack() frame.setVisible(true)
つまり,定数 Java, EXIT_ON_CLOSE とローカル変数 frame, label
を除き,このプログラムにある識別子は実はすべてメソッド名である。
このように "魔法を解いた" かたちにすれば自明なことだが,
長々しく javax.swing.JFrame などと繰り返し書くのが面倒ならば,
include Java swing = javax.swing JFrame = swing.JFrame frame = JFrame.new("窓の例") label = swing.JLabel.new("こんにちは") frame.getContentPane.add(label) frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE) frame.pack frame.setVisible(true)
のように Java のパッケージ名やクラス名 (のように見えるメソッド呼出しの戻り値) を 変数や定数に代入して,それを使い回せばよい。 メソッド呼出しの回数を減らすことになるから,若干の効率改善も期待される。
include Java をせずに二重コロン記法を使って Java::javax.swing.JFrame のようにアクセスすることもできます。 8.8 節も見てください。
8.3 Ruby らしさのための同義語
Java の命名規則は Ruby とは異なる。 jruby は,Java のメソッド名を Ruby の命名規則にしたがって変換した同義語メソッドによって Ruby 話者の便宜を図っている。
setXyz(a)⇒xyz=(a)- 例:
frame.setVisible(true) ⇒ frame.visible = true getXyz()⇒xyz()- 例:
frame.getContentPane ⇒ frame.contentPane abcDefGhi(…)⇒abc_def_ghi(…)- 例:
frame.contentPane ⇒ frame.content_pane
下記はそれを利用した例である。
include Java swing = javax.swing JFrame = swing.JFrame frame = JFrame.new("窓の例") label = swing.JLabel.new("こんにちは") frame.content_pane.add(label) frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.pack frame.visible = true
8.4 Ruby による Java インタフェースの実装
Ruby のクラス定義で Java のインタフェース (の名前のように見えるメソッド呼出しの戻り値) を include し,対応するメソッドを定義することによって,Java のインタフェースを実装できる。
include Java swing = javax.swing JFrame = swing.JFrame frame = JFrame.new("§8.4") button = swing.JButton.new("カウンタ") class ButtonAction include java.awt.event.ActionListener def initialize(button) @button = button @count = 0 end def actionPerformed(evt) @count += 1 @button.text = "%03d" % @count end end button.add_action_listener(ButtonAction.new(button)) frame.content_pane.add(button) frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.pack frame.visible = true
このようなウィンドウが表示される:
ボタンを1回押すとこのようになる:
ボタンをさらに1回押すとこうなる:
8.5 Ruby による Java クラスの拡張
Java のクラス (の名前のように見えるメソッド呼出しの戻り値) を基底クラスとして Ruby のクラスを定義すれば, Ruby で Java のクラスを拡張できる。
include Java Swing = javax.swing JFrame = Swing.JFrame class Button < Swing.JButton include java.awt.event.ActionListener def initialize super("000") @count = 0 add_action_listener(self) end def actionPerformed(evt) @count += 1 self.text = "%03d" % @count end end frame = JFrame.new("§8.5") button = Button.new frame.content_pane.add(button) frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.pack frame.visible = true
swing = javax.swing ではなく
Swing = java.swing としていることに注意してください。
(小文字ではじまる) ローカル変数に代入しているのではなく (大文字ではじまる) 定数として定義しています。
jruby 1.1.6 のバグなのかどうか不明ですが,jruby 1.0.3 と異なり,ローカル変数
swing に代入して
6 行目のクラス定義で基底クラス swing.JButton
を参照すると,undefined method `JButton' for nil:NilClass (NoMethodError) というエラーが発生します。
8.6 import
Java のクラスを表す定数を定義するとき,
JFrame = javax.swing.JFrame
と書くかわりに,Java プログラムと同じように
import javax.swing.JFrame
と書いてよい。これは,括弧を省略せずに書けば,すべてメソッド呼出しの組み合わせ
import(javax().swing().JFrame())
である。
ただし,その結果として得られる JFrame は Ruby の他のクラス名と同じく 定数 である。
前節の例をこの書き方で表すと,次のようになる。
include Java import javax.swing.JFrame import javax.swing.JButton import java.awt.event.ActionListener class Button < JButton include ActionListener def initialize super("000") @count = 0 add_action_listener(self) end def actionPerformed(ev) @count += 1 self.text = "%03d" % @count end end frame = JFrame.new("§8.5.1") button = Button.new frame.content_pane.add(button) frame.default_close_operation = JFrame::EXIT_ON_CLOSE frame.pack frame.visible = true
8.7 配列の相互変換
Ruby の配列は to_java メソッドで Java の配列に変換できる。 メソッドを無引数で呼び出した場合,配列は java.lang.Object[] に変換される。 次の引数を与えて Java の配列の要素型を指定することもできる。
- :boolean
- :byte
- :char
- :short
- :int
- :long
- :float
- :double
- :big_integer
- :object
- :string
現在の実装では,Ruby の配列に不適切な型の要素があっても, かなり強引に型を自動変換する。 この振舞は実用的だが,注意が必要である。 逆変換には to_a メソッドを使う。
下記の jirb セッションでは,Ruby の文字列から配列 a を得て, それを Java の byte 配列 bbに変換してから, to_a で逆変換したり, java.lang.String のコンストラクタに与えて Java の文字列 ss にしている。
irb(main):001:0> a = "the great ruby".unpack("C*")
=> [116, 104, 101, 32, 103, 114, 101, 97, 116, 32, 114, 117, 98, 121]
irb(main):002:0> bb = a.to_java(:byte)
=> [B@ff6313
irb(main):003:0> bb.to_a
=> [116, 104, 101, 32, 103, 114, 101, 97, 116, 32, 114, 117, 98, 121]
irb(main):004:0> ss = java.lang.String.new(bb)
=> #<Java::JavaLang::String:0x61116f @java_object=the great ruby>
irb(main):005:0> ss.to_s
=> "the great ruby"
include Java は不要です。
なお,jruby 1.1.6 では a.to_java(:decimal) はできないようです。
8.8 君の Java ライブラリへのアクセス
非標準的な Java ライブラリへ jruby からアクセスするには,require
メソッドに jar ファイルの名前を文字列として与える。
モジュール Java の関数であるかのように,Java パッケージを参照できる。
ただし,java, javax, com, org 以外で始まるパッケージ名は
Java:: を接頭した二重コロン記法でアクセスする必要がある。
include Java ではアクセスできない。
下記の例は,Java による Lisp インタープリタ L2Lisp 5.0 の
jar ファイルを require "../tmp/L2Lisp.jar"
で取り込み,
LL = Java::jp.co.okisoft.ngo.suzuki611.llsp.LL
によって main 関数を含む LL クラスを得て,そしてその main 関数を実行している。
ここで main 関数の引数 [tf.path].to_java(:string) の tf は,
文字列 script の内容 (簡単な Lisp スクリプトからなる) を書き込んだ一時ファイルである。
そのパス名 tf.path を唯一の要素とする Ruby リストを,to_java(:string)
を使って Java の String[] 値に変換している。
script = ' (princ "1 + 2 = ") (print (+ 1 2)) (princ "100! = ") (print (reduce * (range 1 101))) ' require "tempfile" tf = Tempfile.new("lisp_script") tf.puts(script) tf.close require "../tmp/L2Lisp.jar" LL = Java::jp.co.okisoft.ngo.suzuki611.llsp.LL LL.main([tf.path].to_java(:string))
下記がその実行例である。確かに Lisp スクリプトが実行されている。
$ jruby ll.rb 1 + 2 = 3 100! = 9332621544394415268169923885626670049071596826438162146859296389521759999 32299156089414639761565182862536979208272237582511852109168640000000000000000000 00000 $
おわりに
ここでは jruby から Java のクラスを利用する観点での jruby の特徴と機能を説明した。
本章で述べたほかにも, jruby は java.util.Collection に each イテレータを定義し, Enumerable モジュールを mix-in させているなど, Java のクラスを Ruby らしく簡潔に使う便宜を用意している。 そういった便宜は Ruby の人々が抱く直感に沿って用意されており, 君が Ruby に慣れるにつれ,jruby 経由で Java のクラスもどんどん便利に使えるようになるはずだ。
irb(main):001:0> a = java.util.ArrayList.new
=> #<Java::JavaUtil::ArrayList:0xd1edcc @java_object=[]>
irb(main):002:0> a.add_all [3, 1, 4, 1, 5]
=> true
irb(main):003:0> print a
[3, 1, 4, 1, 5]=> nil
irb(main):004:0> a << 9
=> #<Java::JavaUtil::ArrayList:0xd1edcc @java_object=[3, 1, 4, 1, 5, 9]>
irb(main):005:0> a.to_a
=> [3, 1, 4, 1, 5, 9]
irb(main):006:0> a.inject(1) {|x, y| x * y}
=> 540
jruby の実装はまだ決して完璧ではないが, いわゆる Java から Ruby へ,というあれかこれかの二者択一ではなく, 豊富なライブラリと,飾り気のない簡潔明快な記述という両者の長所を享受する道が, jruby によって開けているといえる。