【问题标题】:Why does Nashorn's optimistic typing produce unpredictable results?为什么 Nashorn 的乐观类型会产生不可预知的结果?
【发布时间】:2016-03-10 15:06:11
【问题描述】:

我一直在尝试使用 Nashorn,并得到了一些关于 eval() 的返回类型的奇怪结果。

// calculator.js
var add = function(a, b) {
    return a + b;
}

还有以下 Java 代码:

// CalculatorTest.java
import java.io.InputStreamReader;
import javax.script.*
import org.junit.*
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;

public class CalculatorTest {

    ScriptEngine nashorn;

    @Before
    public void setup() throws ScriptException {
        NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
        nashorn = factory.getScriptEngine("–optimistic-types=true");
        nashorn.eval(new InputStreamReader(
                getClass().getClassLoader().getResourceAsStream("calculator.js")));
    }

    @Test
    public void addViaInvokeFuntion() throws Exception {
        Object result = ((Invocable) nashorn).invokeFunction("add", 2, 3);
        Assert.assertEquals(5.0, result);
    }

    @Test
    public void addViaSimple() throws Exception {
        Object result = nashorn.eval("2+3");
        Assert.assertEquals(5, result);
    }

    @Test
    public void addViaMoreComplexEval() throws Exception {
        Object result = nashorn.eval(
                "var anotherAdd = function(a, b) {\n" +
                "    return a + b;\n" +
                "};\n" +
                "anotherAdd(2, 3);");
        Assert.assertEquals(5L, result);
    }
}

为什么这些测试会以这种方式成功?

有没有办法“预测” Nashorn 返回类型?

乐观类型不应该这样做吗?

【问题讨论】:

  • 当我和迈克尔一起偶然发现这个问题时,我支持这个问题。更准确地说:如果直接使用eval() 调用操作或函数,为什么会返回Integer,如果从外部js 文件调用函数,为什么会返回Double?我完全清楚,应该使用 Number 并且在 JS 中,一切都是浮动的。但除此之外,为什么一次返回 Integer 而另一次返回 Double

标签: javascript java nashorn


【解决方案1】:

您在这里看到的是因为 Nashorn 基于参数的输入类型(而不是乐观类型本身)创建函数的专用版本的方式。当您通过Invocable 接口调用add 时,您最终会调用专业化add(Object, Object) 而不是add(int, int)。如果参数类型是对象,那么就没有可以利用的乐观情绪;你得到以下字节码:

public static add(Object;Object;Object;)Object; aload 1 aload 2 invokestatic ScriptRuntime.ADD(Object;Object;)Object; areturn

(如果您将--print-code 添加到引擎命令行参数,您可以自己检查这些;另一个有用的是--log=recompile,它将打印已生成的函数的类型特化。)

所以它与在calculator.js 中定义的add 无关,而是您的呼叫站点-Invocable.invokeFunction 被键入为采用所有对象参数这一事实。

ScriptRuntime.ADD(Object, Object) 不会试图过于聪明而缩小其结果的类型;它是通用情况下的慢路径 + 运算符实现,当没有关于参数类型的静态信息时使用,它必须准备好处理 JavaScript 的奇怪情况,例如 "2" + [] 等。它的代码当然可以包含特殊的优化案例,例如“如果操作的结果适合 Integer 返回一个”,但我们认为额外的复杂性不值得麻烦,因为无论如何您已经丢失了输入的静态类型信息。因此,当参数的静态类型为 Object 时,所有数字加法都会返回一个双精度值。

另一方面,如果您执行nashorn.eval("add(2, 3)"),那么它会调用add(int, int) 专门化,这是乐观的,您最终的返回值确实是整数5。

希望对您有所帮助。

【讨论】:

  • 非常感谢@Attila,这真的有助于理解差异! :-)
【解决方案2】:

用于在 实现 方面表示 JS 值的 Java 类型(它不是 ECMAScript 规范的一部分。只要 typeof 表示“数字”并且算术运算按预期工作,任何 Java/ JVM类型应该没问题)。对 ECMAScript 数值使用 Java“int”是一种优化。这种优化不能总是完成。只能进行安全类型推断。 Java 端应该期望为 ECMAScript“数字”值返回任何数字值。这些测试必须写成使用 java.lang.Number 类型。您可以在返回值上调用“intValue”、“doubleValue”方法(在将返回的 Object 转换为 java.lang.Number 之后)并断言预期值。

【讨论】:

  • 是的,正如我已经评论过这个问题,我知道这一点。其实我想知道,为什么直接用eval(..)求值时是Integer,而当加载并执行/求值具有same函数的外部文件时是Double...跨度>
猜你喜欢
  • 2018-02-05
  • 1970-01-01
  • 2011-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-03
  • 2021-11-10
相关资源
最近更新 更多