3
public class ArraysDemo {

    public static void main(String[] args) {

          int[] a = {0, 2, 4, 6, 8};
          int[] b = {10, 12, 14, 16, 18};
          Arrays.setAll(a, i -> b[i]+1);
          System.out.println(Arrays.toString(a));
    }  
}

输出:[11, 13, 15, 17, 19]

使用的setAll()函数来源如下:

public static void setAll(int[] array, IntUnaryOperator generator) {
        Objects.requireNonNull(generator);
        for (int i = 0; i < array.length; i++)
            array[i] = generator.applyAsInt(i);
}

IntUnaryOperator是一个功能接口,这是其源代码的一部分:

public interface IntUnaryOperator {
    int applyAsInt(int operand);
    // rest methods are omitted
}

如果我错了,请纠正我,但我对 Java 中 lambda 表达式的理解是,当我将 lambda 表达式作为参数传递给方法时,会创建并调用setAll()实现该接口的匿名类的对象。并且 lambda 表达式本质上是该方法的实现,所以我相信它会转化为:IntUnaryOperatorgeneratorapplyAsInt()

int applyAsInt(int operand){
    return b[operand]+1;
}

对我来说它可以访问是有道理的,operand因为它作为参数传递array[i] = generator.applyAsInt(i);但是,我不明白它是如何操作b的——它不是作为参数传递的,所以它怎么可能被引用?我错过了什么?

4

3 回答 3

5

原因是它实际上bfinal

实例和静态变量可以在 lambda 的主体中不受限制地使用和更改。然而,局部变量的使用受到更多限制:不允许捕获局部变量,除非它们实际上是最终的,这是 Java 8 中引入的一个概念。非正式地,如果局部变量的初始值从未改变(包括在lambda 表达式的主体)——换句话说,将其声明为 final 不会导致编译失败。有效终结性的概念并没有给 Java 引入任何新的语义;它只是定义最终变量的一种稍微不那么冗长的方法。

来自http://www.lambdafaq.org/can-lambda-expressions-use-variables-from-their-environment/

于 2016-08-29T10:42:23.847 回答
2
      Arrays.setAll(a, i -> b[i]+1);

相当于以下效果:

      Arrays.setAll(a, new IntUnaryOperator() {

              private int[] b$ = b;

              @Override
              public int applyAsInt(int i);
                  return b$[i]+1;
              }
          });

这是一个b与原始变量具有相同名称和值的变量b将被保留。需要此副本,因为原始变量(或新变量)的寿命可能比另一个变量更有限。例如作为局部变量。

为了隐藏现在有两个变量,要求不再将它们分配给(“有效地最终”),这将导致“相同”变量的不同值。

于 2016-08-29T12:30:32.280 回答
1

即使没有 lambda 表达式,例如使用匿名内部类,您也可以捕获周围上下文的值,即

int[] a = {0, 2, 4, 6, 8};
int[] b = {10, 12, 14, 16, 18};
Arrays.setAll(a, new IntUnaryOperator() {
    public int applyAsInt(int i) {
        return b[i]+1;
    }
});

但是有一些差异,尽管与您的问题无关。当使用匿名内部类时,关键字thissuper引用内部类的实例,而在 lambda 表达式中,它们与周围上下文中的含义相同。此外,当内部类访问private周围类的成员时,编译器会插入辅助方法来执行访问,而 lambda 表达式可以自然地访问包含类的成员。

为此,您的 lambda 表达式的代码将被编译为具有以下形式i -> b[i]+1类的合成方法:

private static int lambda$main$0(int[] b, int i) {
    return b[i]+1;
}

并且由于它是您类中的一种方法,因此它可以访问所有成员。

所以运行时生成的类等价于

final class ArraysDemo$Lambda$1 implements IntUnaryOperator {
    final int[] b;
    ArraysDemo$Lambda$1(int[] b) {
        this.b = b;
    }
    public int applyAsInt(int i) {
        return ArraysDemo.lambda$main$0(b, i);
    }
}

重点放在等价词上,而不是暗示它必须完全像这样(更不用说普通Java类无法访问该private方法的事实ArraysDemo.lambda$main$0)。


但这些只是技术细节,最重要的关键点是 lambda 表达式可以有效地访问 final局部变量以及包含它们的类的成员,编译器和运行时环境将确保它工作。您可以简单地将它们视为在定义它们的上下文中评估的函数。

于 2016-08-30T13:11:44.767 回答