为什么Scala中的FunctionN类型是作为AnyRef的子类型而不是AnyVal创建的?

时间:2017-10-08 01:28:00

标签: scala

如果我理解正确,在JVM下,每次使用lambda表达式时,都必须创建一个Object。

为什么要开销?为什么Scala创建者在设计FunctionN类型时选择扩展AnyRef而不是AnyVal?我的意思是,它们本身没有任何真正的“值”,所以函数不应该是具有底层单位表示的值对象(或者包含一些哈希用于相等性检查或其他什么)?我可以想象,不为每个lambda分配一个对象可以在某些代码库中提高性能。

我想到的扩展AnyVal的一个明显缺点是它会禁止子类化函数类型。也许仅仅这一点就不足以扩展AnyVal,但还有其他原因吗?

- 编辑

我理解函数需要关闭其他变量,但我认为将它建模为apply方法的参数更自然,而不是FunctionN对象的字段成员(从而消除了拥有java.lang的必要性)。这部分的对象) - 毕竟,不是在编译时所有已知的变量都被关闭了吗?

- 再次修改

我发现了它;我想到的是'lambda lifting'。

1 个答案:

答案 0 :(得分:6)

调用方法的唯一方法是字节码操作invokevirtual(类上的虚拟调度),invokeinterface(相同,但在接口上),invokespecial(完全调用 给定方法,忽略虚拟查找。用于privatesupernew。)和invokestatic召唤独角兽调用static方法)。 invokespecial已经出局了,因为调用一些函数是抽象函数的对立面。 invokestatic也已出局,因为它本质上是invokespecial克隆,不需要this参数。 invokevirtualinvokeinterface足够相似,我们的目的被视为相同。

没有办法像你在C中看到的那样传递一个普通的函数指针,即使你可以,也永远不能调用它,因为没有指令可以跳转到代码中的任意一点。 (方法中的所有跳转目标都限制在该方法中,并且对外部的所有引用都归结为字符串。(当然,JVM在加载文件后可以自由地优化该表示。)

为了使用任一指令调用方法,JVM必须在目标对象的虚拟调度表中查找方法。如果你试图用()虚拟出对象(AnyVal子类直到2.10才存在,但让我们怀疑我们的怀疑),那么JVM会让你感到非常困惑试图将一种(可能有趣的)方法称为接近"无特征的blob"你可以得到。

还要记住,对象的方法完全取决于它的类。如果a.getClass == b.getClass,则ab具有完全相同的方法,代码和所有内容。为了解决这个问题,我们需要创建FunctionN特征的子类,这样每个子类代表一个函数,每个类的每个实例都包含对其类的引用,该类包含对与之关联的代码的引用。那个功能。即使使用invokedynamic,这仍然是正确的,因为LambdaMetaFactory的当前实现在运行时创建了内部类。

最后,函数不需要状态的假设是错误的,正如@Oleg指出的那样。闭包需要保持对其环境的引用,这只能通过对象实现。