为什么 Nashorn 的乐观类型会产生不可预测的结果?
Why does Nashorn's optimistic typing produce unpredictable results?
我一直在尝试使用 Nashorn,并得到了一些关于 eval()
的 return 类型的奇怪结果。
// 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);
}
}
为什么这些测试以这种方式成功?
有什么方法可以 "predict" Nashorn return 类型吗?
乐观类型不应该这样做吗?
Java 类型用于在 实现 方面表示 JS 值(它不是 ECMAScript 规范的一部分。只要 typeof 表示 "number" 和算术运算按预期工作,任何 Java/JVM 类型都应该没问题)。使用 Java "int" 到 ECMAScript 数字值是一种优化。不能总是进行优化。只能进行安全类型推断。 Java 端应该期望任何数字值被 returned 用于 ECMAScript "number" 值。这些测试必须写成使用 java.lang.Number 类型。您可以在 return 值上调用 "intValue"、"doubleValue" 方法(在将 returned 对象转换为 java.lang.Number 之后)并断言预期值。
你在这里看到的是因为 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" + []
等。它的代码当然可以包含特殊的优化情况,例如 "if the result of the operation fits in an Integer
return one",但我们认为额外的复杂性不值得麻烦,因为您已经丢失了输入的静态类型信息。因此,当参数的静态类型为 Object
.
时,您将获得所有数字加法的双精度值
如果你在另一方面执行 nashorn.eval("add(2, 3)")
那么它会调用 add(int, int)
特化,这是乐观的,你确实以 return 值为整数 5 结束。
希望对您有所帮助。
我一直在尝试使用 Nashorn,并得到了一些关于 eval()
的 return 类型的奇怪结果。
// 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);
}
}
为什么这些测试以这种方式成功?
有什么方法可以 "predict" Nashorn return 类型吗?
乐观类型不应该这样做吗?
Java 类型用于在 实现 方面表示 JS 值(它不是 ECMAScript 规范的一部分。只要 typeof 表示 "number" 和算术运算按预期工作,任何 Java/JVM 类型都应该没问题)。使用 Java "int" 到 ECMAScript 数字值是一种优化。不能总是进行优化。只能进行安全类型推断。 Java 端应该期望任何数字值被 returned 用于 ECMAScript "number" 值。这些测试必须写成使用 java.lang.Number 类型。您可以在 return 值上调用 "intValue"、"doubleValue" 方法(在将 returned 对象转换为 java.lang.Number 之后)并断言预期值。
你在这里看到的是因为 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" + []
等。它的代码当然可以包含特殊的优化情况,例如 "if the result of the operation fits in an Integer
return one",但我们认为额外的复杂性不值得麻烦,因为您已经丢失了输入的静态类型信息。因此,当参数的静态类型为 Object
.
如果你在另一方面执行 nashorn.eval("add(2, 3)")
那么它会调用 add(int, int)
特化,这是乐观的,你确实以 return 值为整数 5 结束。
希望对您有所帮助。