我正在开发一个在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
上下文返回对象元素。
init
方法在 execute
方法之前调用。我做到了:
当我尝试评估
execute
方法时,出现以下异常:
org.jruby.exceptions.NoMethodError: (NoMethodError) undefined method `getValue' for nil:NilClass
我认为我正确地转换为
APIHelper
接口,该接口具有 getValue()
方法。我做错了什么?
仅供参考,这个旧版本的脚本(没有转换)正确评估并返回了正确的结果:
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 变量没有静态类型,您始终可以调用它响应的对象上的任何方法。
在 Java 中,变量和方法参数具有静态类型,并且 Java 编译器不会让您调用对象上的方法,除非它可以通过其静态类型判断该方法实际上应该在那里(或者除非您使用显式强制它)类型转换)。因此,在 Java 中,虽然您可以将 APIHelper 实例分配给 Object 类型的变量(因为 Java 中的每个非基本类型都是 Object 的子类),但您实际上无法对该 Object 变量调用任何 APIHelper 方法,除非您首先显式地调用将其转换回 APIHelper 类型(如果它实际上不是 APIHelper 实例,可能会引发 ClassCastException)。
在 Ruby 中变量没有静态类型。当您尝试调用对象的方法时,Ruby 解释器会在运行时检查该对象是否响应该方法,如果没有响应,则会调用该方法或引发 NoMethodError。 (实际上,在引发错误之前,它会首先尝试在对象上调用 method_missing
,以防万一它最终能够处理该调用!)用 Java 术语来说,你可以sort of 想到这一点就像解释器总是自动将每个对象转换为其最具体的可能类型一样。 当然,在 Java 中,您可以使用 reflection
来模拟这种动态方法查找和转换,事实上,这正是 JRuby 在调用 Java 对象上的方法时所做的事情。 (它实际上做的事情
远不止这些,包括在 Ruby 和 Java 方法命名约定之间进行转换,并允许您在 Ruby 中重新打开 Java 类并向其添加 Ruby 方法,甚至将整个 Ruby 模块注入到 Java 类中。当然,此类修改仅在 Ruby 方面可见,因为它们仅修改 Java 类的 Ruby 包装器。)
无论如何,这里要传达的信息是,您基本上需要在 JRuby 中调用 to_java
,除非在一种特定情况下:当调用根据其参数类型而表现不同的多态 Java 方法时,有时您如果方法调用不明确,则需要显式告诉 JRuby 参数应转换为哪种 Java 类型。使用
to_java
可以有所帮助,尽管在这些情况下 java_send
通常会更好。(此外,JRuby 中的 arrays
有一个特殊的 to_java
实现,可以转换元素,有时很有用。)