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

j4rs Instance

Rustj4rsのInstanceのメモ。


概要

j4rsのInstanceは、Javaのインスタンスを表す構造体

use j4rs::Instance;

Jvm::invoke()(メソッド呼び出し)等の戻り値がInstanceで返る。


メソッド呼び出しの引数としてInstanceを渡したい場合はInvocationArgに変換する。


InstanceをRustの型の値に変換したい場合はJvm::to_rust()を使う。
Rustの型の値に変換する例


Instanceを複製したい場合はJvm::clone_instance()を使う。
j4rsのメソッドの中にはInstanceの所有権を奪うものがあるので、奪われたくない場合は複製したInstanceを渡せばいい。


class_nameメソッド

Instanceにはclass_name()というクラス名を取得できるメソッドがあるが、常にきちんとしたクラス名が取得できるとは限らない。

Rust側でクラス名を指定してInstanceが作られた場合はそのクラス名が返るようだが、
Jvm::invoke()等で返されたInstance(Javaの中で作られた値に対するInstance)の場合は「known_in_java_world」という文字列が返る。


InstanceをRustの型の値に変換する方法

Instanceの値をRustの型の値に変換するにはJvm::to_rust()を使う。
なお、to_rust()はInstanceの所有権を奪うので注意。

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

Javaとしての型がintであるInstanceに対しては、i32で取得できる。
(整数型に関しては、厳密に同じ型でなくても大丈夫なようだ。すなわち、intに対しi64・u64やi16でも取得できる)

Javaのintの値をRustのStringとして取得することは出来ないので、この例を「let length: String = jvm.to_rust(length)?;」に変えると、実行時にエラーになる。


バイト配列を変換したい場合、Rust側はVec<i8>。

    let byes: Vec<i8> = jvm.to_rust(bytes)?;

nullチェックを行う方法

Instanceの値がnullかどうか判定するには、Jvm::check_equals()を使ってNull列挙型と比較する。

use j4rs::Null;
    let mut rmap: HashMap<String, i32> = HashMap::new();
//  rmap.insert(String::from("abc"), 123);

    let map = jvm.java_map("java.lang.String", "java.lang.Integer", rmap)?;
    let r = jvm.invoke(&map, "get", &[InvocationArg::try_from("abc")?])?;
    let check = jvm.check_equals(&r, InvocationArg::create_null(Null::Of("java.lang.Integer"))?)?;

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


キャストの必要性

以下のようなコードを考えてみる。

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

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

    let length = jvm.invoke(&s, "length", InvocationArg::empty())?;	// var length = s.length();
                     ↓
                     org.astonbitecode.j4rs.errors.InvocationException: While invoking method length of Class java.lang.Object
                     Caused by: java.lang.NoSuchMethodException: Method length was not found in java.lang.Object or its ancestors.

これを実行すると、「java.lang.Objectにlengthというメソッドは無い」というエラーが出る。
Stringに対してlengthメソッドを呼んだつもりなのに、なぜObject??

通常のJavaのソースコードならば、「List<String> list」に対してgetメソッドを呼んだら、戻り型はStringである。
しかしコンパイルされたJavaのバイトコードのレベルでは、型引数の情報は消えて、Objectになっている。
なので、j4rsのinvokeでは、返ってきた値の型はObjectとして認識されているのだと思われる。
それなら、Objectにlengthメソッドは無いので、エラーになって当然だ。

この場合、表面上はObject型だが、インスタンスの実体はちゃんとStringなので、Stringにキャストしてやれば大丈夫。

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

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


引数の型が一致しなくてNoSuchMethodExceptionが発生するケース


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