InvocationArgは、メソッド呼び出しの引数を表す列挙型。
use j4rs::InvocationArg;
InvocationArgは「Javaの型」と「値」をペアで保持する。
Javaでメソッドを呼び出す際は、「メソッド名」と「引数の型」が一致するメソッドを探し、呼び出すメソッドを決定する。
なので、引数の型が必須となる。
そのためにInvocationArgは値と一緒に型も保持しているのだろう。
Instanceは値のみを保持しているので、引数として使うにはInvocationArgに変換する必要がある。
InvocationArgのメソッド(抜粋)。
ここでのResultは、Jvmのメソッド一覧と同じく、j4rs::errors::Result。
メソッド | 説明 | 例 | Java相当 |
---|---|---|---|
empty () -> &[InvocationArg;0] |
引数が無いときに指定するInvocationArgを返す。 | let s = jvm.create_instance("java.lang.String",
InvocationArg::empty())?; |
var s = new java.lang.String(); |
into_primitive (self) -> Result<InvocationArg> { |
プリミティブ型に変換する。 →例 |
let n0 = InvocationArg::try_from(0)?; |
var n0 = Integer.valueOf(0); |
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"))?; |
|
from (instance: Instance) -> InvocationArg |
InstanceをInstanceArgに変換する。 (Instanceの所有権を奪うので注意) →例 |
let s = jvm.create_instance("java.lang.String",
InvocationArg::empty())?; |
|
try_from (arg: T) -> Result<InvocationArg> |
色々な型(整数や文字列など)の値をInstanceArgに変換する。 →プリミティブ型の注意点 |
let n = InvocationArg::try_from(123)?; |
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のメソッドを呼び出す(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を渡すときは、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
と書くことも出来る。
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が出たら、引数の型を確認してみるのが良いだろう。