Techioz Blog

JRuby の nil:NilClass の未定義メソッド

概要

Java にスクリプト言語を統合するフレームワークを開発しています。 Ruby については、JRuby (最新バージョン 9.4.5.0) を使用しています。

次の Java インターフェイスがあります。

public interface ContextListener {
   public void init(ScriptContext context);
}

そして

public interface Script {
   public int execute();
}

そして

public class APIHelper {
   public int getValue() {
      return 10;
   }
}

そして

public class ScriptContext {
   Map<String, Object> additionalHelpers = new HashMap<>();

   public ScriptContext() {
       this.additionalHelpers.put("api", new APIHelper());
   }

   public Object getAdditionalHelper(String name) {
      return additionalHelpers.get(name);
   }
}

私のRubyスクリプトは次のとおりです。

require 'java'
java_import 'org.scripthelper.context.ScriptContext'
java_import 'org.scripthelper.context.ScriptHelper'
java_import 'org.scripthelper.context.ContextListener'
java_import 'org.scripthelper.ruby.addhelpers.APIHelper'
class ScriptClass
java_implements 'org.scripthelper.context.ContextListener', 'org.scripthelper.ruby.addhelpers.Script'
    attr_reader :context
    attr_reader :api
    def init(ctx)
       context = ctx
       obj = ctx.getHelper("api")
       @api = obj.to_java(Java::org::scripthelper::ruby::addhelpers::APIHelper)
    end
  def execute()
    a = @api.getValue()
    return a + 1
  end
end

コンテキストは Object 要素を返します。 init メソッドは、execute メソッドの前に呼び出されます。やった:

実行メソッドを評価しようとすると、次の例外が発生します。

org.jruby.exceptions.NoMethodError: (NoMethodError) undefined method `getValue' for nil:NilClass

getValue() メソッドを持つ APIHelper インターフェイスへのキャストが正しく行われたと思いました。私が何を間違えたのでしょうか?

ちなみに、この古いバージョンのスクリプト (キャストなし) は正しく評価され、正しい結果が返されました。

require 'java'
java_import 'org.scripthelper.context.ScriptContext'
java_import 'org.scripthelper.context.ScriptHelper'
java_import 'org.scripthelper.context.ContextListener'
class ScriptClass
java_implements 'org.scripthelper.context.ContextListener', 'org.scripthelper.ruby.addhelpers.Script'
    attr_reader :context
    def init(ctx)
       context = ctx
    end
  def execute()
    a = 1
    return a + 1
  end
end

解決策

.to_java 呼び出しを削除し、 @api = ctx.getHelper(“api”) と設定するだけでも機能しますか?

Ruby は動的に型指定される言語であるため、Ruby 変数には静的な型がなく、応答するオブジェクトのメソッドをいつでも呼び出すことができることに注意してください。

それを APIHelper 型にキャストして戻します (実際には APIHelper インスタンスでない場合は ClassCastException が発生する可能性があります)。Java では、変数とメソッド引数には静的型があり、Java コンパイラは、メソッドが実際に存在する必要があることをその静的型で判断できない限り (または明示的な型で強制しない限り)、オブジェクトのメソッドを呼び出すことはできません。キャスト)。したがって、Java では、APIHelper インスタンスを Object 型の変数に割り当てることができますが (Java のすべての非プリミティブ型は Object のサブクラスであるため)、最初に明示的に指定しない限り、その Object 変数に対して APIHelper メソッドを実際に呼び出すことはできません。

Rubyでは変数には静的型がありません。オブジェクトのメソッドを呼び出そうとすると、Ruby インタプリタは、そのオブジェクトがそのメソッドに応答するかどうかを実行時にチェックし、メソッドを呼び出すか、応答しない場合は NoMethodError を発生させます。 (実際には、エラーを発生させる前に、最終的に呼び出しを処理できる可能性がある場合に備えて、最初にオブジェクトに対して method_missing の呼び出しを試みます。) Java 用語で言えば、これはインタープリターが常に自動的にキャストするように考えることができます。すべてのオブジェクトを最も具体的な可能なタイプに変換します。

もちろん、Java では、リフレクションを使用してこの種の動的なメソッドの検索とキャストをエミュレートできます。実際、これはまさに、Java オブジェクトのメソッドを呼び出すときに JRuby が内部で行うことです。

(実際には、Ruby と Java メソッドの命名規則の間の変換や、Ruby で Java クラスを再度開いてそこに Ruby メソッドを追加したり、Ruby モジュール全体を Java クラスに挿入したりすることも可能にするなど、それ以外にも多くのことを行います。もちろん、そのような変更は可能です。 Java クラスの Ruby ラッパーを変更するだけなので、Ruby 側でのみ表示されます。)

とにかく、ここでの重要なメッセージは、1 つの特定の場合を除いて、基本的に JRuby で to_java を呼び出す必要はないということです。引数の型に応じて動作が異なるポリモーフィックな Java メソッドを呼び出すときは、JRuby に明示的に指示する必要がある場合があります。メソッド呼び出しがあいまいになる場合に、引数をどの Java タイプにキャストする必要があるか。 to_java を使用すると役に立ちますが、そのような場合は java_send の方が優れていることがよくあります。

(また、JRuby の配列には要素を変換する to_java の特別な実装があり、これが役立つ場合があります。)