8. JRuby から Java へのアクセス

ここでは jruby 1.0.3 に固有の特徴を説明する。 その目的は,君が jruby を Java のクラスを利用するための簡易な言語として使えるようにすることである。

8.1 文字エンコーディング

今のところ jruby では,文字を UTF-8 扱いするか,Ruby のデフォルトどおり 1 バイトずつの列として扱うか, 二通りの指定が形式上可能である (少なくとも jruby 内部で指定を受理する部分 jruby-1.0.3/src/org/jruby/util/KCode.java はそう作られている)。 ただし,現状ではどちらの指定もほとんど意味を持たない。

UTF-8 で書かれた

p /^あ*$/ === "ああ"

に対し,デフォルトの ruby は false を印字するが,ruby -Ku は true を印字する。 正規表現でゼロ回以上の繰り返しを表す * が UTF-8 の3バイト文字「あ」 の最後のバイトだけに掛かるか,それとも3バイト全体に掛かるかの違いである。 しかし,jruby は -Ku オプションを与えても false を印字する。

UTF-8 で書かれた

p "あ"

に対し,デフォルトの ruby は "\343\201\202" を印字するが,ruby -Ku は "あ" を印字する。 しかし,jruby は -Ku オプションを与えても "\343\201\202" を印字する。

グローバル変数 $KCODE"u" を代入したときも同様である。

ただし, いずれの場合でも Ruby による単純な文字列入出力 (putsgets など) では,ruby と同じく jruby も文字列をバイト透過に扱うから,バイナリ・データとして漢字や かな などの非 ASCII 文字を扱うことはできる。

さらに重要なこととして,-K オプションや $KCODE の指定にかかわらず, Java との相互作用では,Ruby の文字列はつねに UTF-8 が仮定される。

include Java
java.lang.System.out.println("あ")

は,たとえ Java のデフォルト文字エンコーディングが Shift_JIS であるようなプラットフォームであっても, UTF-8 で書かれているときに限り,Java に正しく「あ」という文字として渡される (この例では Java は,渡された「あ」をプラットフォームのデフォルト文字エンコーディング (例えば Shift_JIS) で印字する)

したがって,現状では jruby のプログラムは UTF-8 で記述するのが妥当 である。 ただし,正規表現と inspect でマルチバイト文字扱いされない ことに注意する。

将来できるかもしれない,きちんとした -K オプションの実装にそなえての縁起物 :-) として 今 -Ku を起動オプションに与えるべきかどうかは微妙です…。

8.2 標準 Java ライブラリへのアクセス

下記を行うことによって標準 Java ライブラリへのアクセスが可能になる。 旧バージョン以来の方法もあるが,今はこの簡潔な方法を使えばよい。 (参照: http://wiki.jruby.org/wiki/Calling_Java_from_JRuby)

include 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)

のようにパッケージ名やクラス名 (のように見えるメソッド呼出しの戻り値) を変数や定数に代入して,それを使い回せばよい。

本当のことを言えば,include Java をせずに,二重コロン記法を使って Java::javax.swing.JFrame のようにアクセスすることもできます。 8.7 節で述べるように,実はこちらのほうが汎用性があります。

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

8.6 配列の相互変換

Ruby の配列は to_java メソッドで Java の配列に変換できる。 メソッドを無引数で呼び出した場合,配列は java.lang.Object[] に変換される。 次の引数を与えて Java の配列の要素型を指定することもできる。

現在の実装では,Ruby の配列に不適切な型の要素があっても, かなり強引に型を自動変換する。 この振舞は実用的だが,注意が必要である。 逆変換には to_a メソッドを使う。

下記の jirb セッションでは,Ruby の文字列から配列 a を得て, それを Java の byte 配列 bbに変換してから, to_a で逆変換したり, java.lang.String のコンストラクタに与えて Java の文字列 ss にしている。

irb(main):021:0> a = "the great ruby".unpack("C*")
=> [116, 104, 101, 32, 103, 114, 101, 97, 116, 32, 114, 117, 98, 121]
irb(main):022:0> bb = a.to_java(:byte)
=> #<#<Class:01x9104ed>:0x7a07ac @java_object=[B@8de768>
irb(main):023:0> bb.to_a
=> [116, 104, 101, 32, 103, 114, 101, 97, 116, 32, 114, 117, 98, 121]
irb(main):024:0> ss = java.lang.String.new(bb)
=> #<Java::JavaLang::String:0xbbd743 @java_object=the great ruby>
irb(main):025:0> ss.to_s
=> "the great ruby"

8.7 君の Java ライブラリへのアクセス

君の Java ライブラリへ jruby からアクセスするには,require メソッドに jar ファイルの名前を文字列として与えればよい。 モジュール Java の関数であるかのように,君の Java パッケージを参照できる。 ただし,java, javax, com, org 以外で始まるパッケージ名については, 名前の検索トリガを機能させるためか,あくまで 二重コロン記法 Java:: でアクセスする必要がある。 include Java をしてもアクセスできない。

下記の例では Java による Lisp インタープリタ L2Lisp 5.0LL.java のクラス LL を Ruby 定数 LL に得て,その main(String[] args) メソッドを呼び出している。

一時ファイル tf に Lisp スクリプト script の内容を書き込み, そのパス名 tf.path を配列要素とする Java の String 配列を [tf.path].to_java(:string) で作成し,Java の main メソッドに渡す。 main メソッドの仮引数の型 String[] との不一致を防ぐため, to_java(:string):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.Collectioneach イテレータを定義し, Enumerable モジュールを mix-in させているなど, Java のクラスを Ruby らしく簡潔に使う便宜を用意している。 そういった便宜は Ruby の人々が抱く直感に沿って用意されており, 君が Ruby に慣れるにつれ,jruby 経由で Java のクラスもどんどん便利に使えるようになるはずだ。

irb(main):011:0> a = java.util.ArrayList.new
=> #<Java::JavaUtil::ArrayList:0x85dfcf @java_object=[]>
irb(main):012:0> a += [3, 1, 4, 1, 5]
=> #<Java::JavaUtil::ArrayList:0x5ee9d9 @java_object=[3, 1, 4, 1, 5]>
irb(main):013:0> print a
[3, 1, 4, 1, 5]=> nil
irb(main):014:0> a << 9
=> #<Java::JavaUtil::ArrayList:0x5ee9d9 @java_object=[3, 1, 4, 1, 5, 9]>
irb(main):015:0> a.to_s
=> "[3, 1, 4, 1, 5, 9]"
irb(main):016:0> a.inject(1) {|x, y| x * y}
=> 540

jruby の実装はまだ決して完璧ではないが, いわゆる Java から Ruby へ,というあれかこれかの二者択一ではなく, 豊富なライブラリと,飾り気のない簡潔明快な記述という両者の長所を享受する道が, jruby によって開けているといえる。


戻る


Copyright (c) 2007 Oki Software Co., Ltd.