Java 8 Lambda构造和JavaScript之间的确切区别是什么?

8

我需要将Java 8代码转换为JavaScript(单向,一次性)。为了加快速度,我希望尽可能自动化,并在之后使用测试套件来解决所有剩余的问题。

我想知道Java 8 lambda和JavaScript(函数)之间的区别是什么?

是否存在重要的不兼容性?

2个回答

12

比较 Java lambdas 和 JS 函数时需要注意两者对作用域的处理方式。

在 Java 中,lambdas 只能访问 effectively final 变量,并且只捕获那些明确需要的变量。在 JS 中,函数可以访问所有父闭包中的变量,因此捕获了所有内容。

由此产生的一个结果是可能会发生内存泄漏,除非您知道自己在做什么。例如:

IntSupplier getSupplier(MyMemoryHeavyClass heavy) {
    int x = heavy.hashCode();
    return () -> x;
}

这种方法将返回一个仅包含 int 的 lambda。这里没有问题。然而,直接翻译为 JavaScript 的话......

function getSupplier(heavy) {
    var x = heavy.hashCode();
    return function() { return x; };
}

乍一看可能不明显,但这有一个重大问题。函数表达式将捕获范围内的所有内容,包括heavy参数(即使它在返回的函数中没有被引用)。因此,它会阻止需要占用大量内存的heavy被垃圾回收,从而会在返回的函数存在期间一直保留在内存中。

编辑

正如评论中指出的那样,这些信息可能有点过时,因为现代引擎似乎更加智能化。例如,V8将仅捕获其认为必要的内容。但是,由于在同一范围内创建的所有函数似乎共享相同的闭包,因此仍然可能会被欺骗。

例如,添加行(function y() { return heavy; });,否则几乎不起作用,将强制heavy进入与return x;函数使用相同的闭包,从而创建泄漏。

虽然在这个特定示例中有些牵强,但是在简单地翻译包含多个Lambda的Java方法时,类似的问题并不难发生。


1
我不确定JS运行时是否需要保持heavy的活动状态。尽管即使他们将其优化掉,那也是实现特定的行为,而不是规范所保证的。 - the8472
2
你确定JS运行时不会对此进行优化吗?这意味着很难在没有捕获整个作用域的情况下正确地实现JS函数。是否存在某种情况,静态分析工具无法确定匿名函数是否使用了“heavy”? - Didier L
1
随意寻找一个能够捕捉所有内容的引擎。祝你好运。 - a better oliver
1
看起来你是正确的。我已经修改了我的答案以反映这一点。 - BambooleanLogic

4

JavaScript函数/箭头函数表达式和Java 8匿名类/lambda之间的一个主要区别是,后者(Java)不能重新分配捕获的变量和参数,因为它们在Java 8中被认为是有效地最终的(在先前的Java版本中,所有捕获的变量和参数必须声明为final)。有一些解决此限制的方法:

  • 使用自定义的 Mutable<T>,并使用 setget 方法作为 Mutable<?> 实例,因为可能会将其作为捕获变量或参数传递。
  • 使用,比如说,AtomicReference<T> 或一些类似的 Atomic*** 方法(如果我没记错的话,这种方法在 GWT 中不能使用)。
  • 使用单元素数组作为值盒子,因为数组元素可以很容易地重新分配,比如说 Object[] o = {null}。尽管这种解决方法似乎更低级,并且需要通过索引进行仔细的重新分配/引用,但它可能具有以下优点:1)它可以完美处理基本类型(比如说,new int[1] 明显比 Mutable<Integer> 更好)。2)一个数组可以保存多个值(比如说,你的 lambda 表达式可能想要修改捕获的 rgb,并且三元组可以被包装在 float[] rgb = new float[3] 中,其中 rgb[0] 可以是 r)。如果我没记错的话,这种解决方法是 IntelliJ IDEA 中的建议意图。
因此,如果您的Java代码中有上述解决方法,请在转换为JavaScript时尝试摆脱这些解决方法。假设以下Java代码具有原始数组框:
final float rgb[] = new float[3];
final Runnable whiteOut = () -> {
    rgb[0] = 255;
    rgb[1] = 255;
    rgb[2] = 255;
};
final Runnable blackOut = new Runnable() {
    @Override
    public void run() {
        rgb[0] = 255;
        rgb[1] = 255;
        rgb[2] = 255;
    }
};
whiteOut.run();
blackOut.run();

如果您可以接受分开的rgb,则可以轻松将其转换为更清晰的JavaScript代码:

var r, g, b;
var whiteOut = () => { r = 255; g = 255; b = 255; }
var blackOut = function() { r = 0; g = 0; b = 0; }

whiteOut();
blackOut()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接