S-JIS[2024-09-21] 変更履歴

j4rs InvocationArg

Rustj4rsのInvocationArgのメモ。


概要

InvocationArgは、メソッド呼び出しの引数を表す列挙型

use j4rs::InvocationArg;

InvocationArgは「Javaの型」と「値」をペアで保持する。


Javaでメソッドを呼び出す際は、「メソッド名」と「引数の型」が一致するメソッドを探し、呼び出すメソッドを決定する。
なので、引数の型が必須となる。
そのためにInvocationArgは値と一緒に型も保持しているのだろう。

Instanceは値のみを保持しているので、引数として使うにはInvocationArgに変換する必要がある。


InvocationArgのメソッド

InvocationArgのメソッド(抜粋)。

ここでのResultは、Jvmのメソッド一覧と同じく、j4rs::errors::Result

メソッド 説明 Java相当
empty() -> &[InvocationArg;0] 引数が無いときに指定するInvocationArgを返す。 let s = jvm.create_instance("java.lang.String", InvocationArg::empty())?;
let length = jvm.invoke(&s, "length", InvocationArg::empty())?;
var s = new java.lang.String();
var length = s.length();
into_primitive(self) -> Result<InvocationArg> { プリミティブ型に変換する。
let n0 = InvocationArg::try_from(0)?;
let n = n0.into_primitive()?;
var n0 = Integer.valueOf(0);
var n = n0.intValue();
instance(self) -> Result<Instance> Instanceに変換する。
Javaから返されたInstanceをInvocationArgに変換していた場合のみ使用可能。
let i = arg.instance()?;  
class_name(&self) -> &str クラス名を取得する。    
create_null(null: Null) -> Result<InvocationArg> null引数を生成する。
InvocationArgは値と一緒に型も保持するので、nullであっても型を明示する必要がある。
let null_arg = InvocationArg::create_null(Null::Of("java.lang.String"))?;
let null_arg = InvocationArg::create_null(Null::String)?;
 
from(instance: Instance) -> InvocationArg InstanceをInstanceArgに変換する。
(Instanceの所有権を奪うので注意)
let s = jvm.create_instance("java.lang.String", InvocationArg::empty())?;
let s = InvocationArg::from(s);
 
try_from(arg: T) -> Result<InvocationArg> 色々な型(整数や文字列など)の値をInstanceArgに変換する。
プリミティブ型の注意点
let n = InvocationArg::try_from(123)?;
let s = InvocationArg::try_from("abc")?;
 

プリミティブ型の例

InvocationArg::try_from(整数)によって作られるデータは、Javaとしての型が整数のラッパークラスになる。

例えばInvocationArg::try_from(0)は「java.lang.Intgerの0」という値になる。
これを「引数の型がintであるメソッド」に渡すと、型が一致しないので呼び出すべきメソッドが見つけられず、実行時エラー(java.lang.NoSuchMethodException)になってしまう。

    let n = InvocationArg::try_from(0)?;	// Integerの0
    let r = jvm.invoke_static("java.lang.Integer", "valueOf", &[n])?;	// valueOf(Integer)が見つからなくて、java.lang.NoSuchMethodExceptionが発生する

into_primitive()でプリミティブ型に変換することで、型が一致するようになる。

    let n = InvocationArg::try_from(0)?;	// Integerの0
    let n = n.into_primitive()?;      	// intの0
    let r = jvm.invoke_static("java.lang.Integer", "valueOf", &[n])?;	// valueOf(int)が呼ばれる

Javaのメソッドを呼び出す例

Javaのメソッドを呼び出す(invokeする)例。


引数があるメソッドを呼び出す例

引数があるJavaメソッドを呼び出す例。

use std::collections::HashMap;
    let map: HashMap<String, i32> = HashMap::new();
    let map = jvm.java_map("java.lang.String", "java.lang.Integer", map)?;	// new java.util.HashMap<String, Integer>();

    let key = InvocationArg::try_from("abc")?;
    let value = InvocationArg::try_from(123)?;
    jvm.invoke(&map, "put", &[key, value])?;		// map.put("abc", 123)

Jvm::invoke()等には、引数として、InvocationArgのスライス(可変長配列のようなもの)を渡す。(「&[key, value]」の部分)


InvocationArgを参照で渡すことも出来る。

    jvm.invoke(&map, "put", &[&key, &value])?;	// map.put("abc", 123)

参照で渡しておけば所有権を奪われないので、この後も変数key, valueを使うことが出来る。


引数が無いメソッドを呼び出す例

引数が無いJavaメソッドを呼び出す際は、Jvm::invoke()等の引数としてInvocationArg::empty()が使える。

    let s = jvm.create_instance("java.lang.String", InvocationArg::empty())?;	// new java.lang.String()
    let length = jvm.invoke(&s, "length", InvocationArg::empty())?;          	// s.length()

引数として「空のスライス」を渡せばいいのだが、そのまま書くとコンパイルエラーになってしまう。
(型が曖昧だということらしい)

×  let s = jvm.create_instance("java.lang.String", &[])?;
×  let length = jvm.invoke(&s, "length", &[])?;

asを使って型を明示してやれば通るけど、これを書くくらいだったらInvocationArg::empty()を使う方が良いだろう^^;

    let s = jvm.create_instance("java.lang.String", &[] as &[InvocationArg])?;
    let length = jvm.invoke(&s, "length", &[] as &[InvocationArg])?;

引数にnullを渡す例

引数にnullを渡すときは、InvocationArg::create_null()を使ってnull用のInvocationArgを作る。

本来のJavaでは、nullの型はどのオブジェクトにも代入できるという特殊な型だが、InvocationArgはメソッドの引数の型の判定に使うので、そのための型情報が必須。
なので、Null列挙型を使って、どの型のnullなのかを明示する。

use j4rs::Null;
    let null_arg = InvocationArg::create_null(Null::Of("java.lang.Integer"))?;
    let is_null = jvm.invoke_static("java.util.Objects", "isNull", &[null_arg])?;
    let is_null: bool = jvm.to_rust(is_null)?;

ちなみに、Null列挙型にはよく使われるクラスに対する列挙子が個別に定義されているので、Null::Of("java.lang.Integer")Null::Integerと書くことも出来る。


Instanceがnullかどかをチェックする方法


引数にInstanceを渡す例

InvocationArg::from()InstanceからInvocationArgに変換することが出来るが、fromメソッドはInstanceの所有権を奪ってしまう。
つまり、その後そのInstanceを使用できなくなる。

    let n = InvocationArg::try_from(123)?;
    let s = jvm.invoke_static("java.lang.String", "valueOf", &[n])?;	// Instance取得

    let system_out = jvm.static_class_field("java.lang.System", "out")?;

    let arg1 = InvocationArg::from(s);		// ここでsの所有権が無くなる
    jvm.invoke(&system_out, "println", &[arg1])?;

    let arg2 = InvocationArg::from(s);		// sを使いたいが所有権が無いのでコンパイルエラー
    jvm.invoke(&system_out, "println", &[arg2])?;
回避策
Instanceを複製する
    let dup1 = jvm.clone_instance(&s)?;    		// Instanceを複製する
    let arg1 = InvocationArg::from(dup1);		// 複製を使う
    jvm.invoke(&system_out, "println", &[arg1])?;

    let arg2 = InvocationArg::from(s);
    jvm.invoke(&system_out, "println", &[arg2])?;
InvocationArgから
Instanceに変換する
    let arg1 = InvocationArg::from(s);
    jvm.invoke(&system_out, "println", &[&arg1])?;	// invokeへはarg1の参照を渡す
    let s = arg1.instance()?;                    	// arg1をInstanceへ変換する

    let arg2 = InvocationArg::from(s);          	// 再びsが使える
    jvm.invoke(&system_out, "println", &[&arg2])?;
InvocationArgを使い回す
    let arg = InvocationArg::from(s);
    jvm.invoke(&system_out, "println", &[&arg])?;	// invokeへはargの参照を渡す
    jvm.invoke(&system_out, "println", &[&arg])?;

メソッドが見つからない例

InvocationArgの型とメソッドの引数の型が一致しないと、呼び出すメソッドが見つけられなくてjava.lang.NoSuchMethodExceptionが発生する。
単純にRustコード上で記述した型やメソッド名が間違っていただけなら原因はすぐ分かるが、たまに不思議なケースがある。

    let arg = InvocationArg::try_from("123")?;
    let list = jvm.invoke_static("java.util.List", "of", &[arg])?;	// List<String> list = List.of("123");

    let index = InvocationArg::try_from(0)?.into_primitive()?;
    let s = jvm.invoke(&list, "get", &[index])?;                       	// String s = list.get(0); を想定

    let s = InvocationArg::from(s);
    let n = jvm.invoke_static("java.lang.Integer", "parseInt", &[s])?;	// var n = Integer.parseInt(s);
                ↓
                org.astonbitecode.j4rs.errors.InvocationException: Error while invoking method parseInt of Class java.lang.Integer
                Caused by: java.lang.NoSuchMethodException: Method parseInt was not found in java.lang.Integer or its ancestors.

「java.lang.IntegerにparseIntメソッドが無い」というエラーだけど、無いわけないやろ!!
実は、メソッド名は問題ないんだけど、InvocationArgが保持している型が想定外のものになっているので、引数の型が合わなくて呼び出すメソッドを見つけられなかったのだ。
(メソッドを探す際の引数の型がエラーメッセージに出てくれれば、原因がすぐ分かるんだけどなぁ)

このケースでは、list.get()の戻り値がObject型として返ってきている。(→Object型が返る理由
だからparseInt(Object)を探していて、そんなメソッドは無いからエラーになっている。
正しくはparseInt(String)。なので、引数の値(Instance)をStringにキャストしてやれば通る。

    let s = jvm.cast(&s, "java.lang.String")?;
    let s = InvocationArg::from(s);
    let n = jvm.invoke_static("java.lang.Integer", "parseInt", &[s])?;	// var n = Integer.parseInt(s);

ジェネリクス以外のケースでも、思ったのと違う型のInstanceが返ってくることがあるようなので、不可解なNoSuchMethodExceptionが出たら、引数の型を確認してみるのが良いだろう。


j4rsへ戻る / Rustへ戻る / 技術メモへ戻る
メールの送信先:ひしだま