当我将lambda表达式作为参数传递它可以访问此范围内的其他变量时,怎么可能?

时间:2016-08-29 10:38:18

标签: java arrays lambda interface functional-interface

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()方法时,实现{{1的匿名类的对象创建接口并将其称为IntUnaryOperator。 lambda表达式本质上是generator方法的一个实现,所以我相信它会转化为:

applyAsInt()

我觉得它可以访问int applyAsInt(int operand){ return b[operand]+1; } ,因为它在operand中作为参数传递但是,我不知道它如何操纵array[i] = generator.applyAsInt(i); - 它不是作为参数传递,所以如何引用它?我错过了什么?

3 个答案:

答案 0 :(得分:5)

原因是b 有效最终

  

可以使用和更改实例和静态变量   限制在一个lambda的身体。使用局部变量,   但是,更受限制:不允许捕获局部变量   除非它们是有效的最终版本,否则Java 8中引入了一个概念。   非正式地,局部变量如果是初始值则实际上是最终的   永远不会改变(包括在lambda表达式的主体内)-in   换句话说,声明最终不会导致编译失败。   有效终结的概念不会引入任何新的语义   到Java;它只是一种稍微冗长的定义最终版本的方式   变量

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

答案 1 :(得分: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相同。需要此副本,因为原始变量(或新变量)的寿命可能比另一个变量更有限。例如,是一个局部变量。

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

答案 2 :(得分: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表达式可以自然地访问包含类的theur成员。

为了实现这一点,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表达式可以有效地访问最终的局部变量及其包含类的成员,并且编译器和运行时环境将确保它作品。您可以简单地将它们视为在定义它们的上下文中进行评估的函数