Spark中Lambda表达式的变量做用域

一般,咱们但愿可以在lambda表达式的闭合方法或类中访问其余的变量,例如:java

package java8test;安全

 

public class T1 {数据结构

    public static void main(String[] args) {多线程

        repeatMessage("Hello"20);闭包

    }并发

    public static void repeatMessage(String text,int count){this

        Runnable r = () -> {spa

            for(int i = 0; i < count; i++){.net

                System.out.println(text);线程

                Thread.yield();

            }

        };

        new Thread(r).start();

    }

}

注意看lambda表达式中的变量count和text,它们并无在lambda表达式中被定义,而是方法repeatMessage的参数变量。若是你思考一下,就会发现这里有一些隐含的东西。lambda表达式可能会在repeatMessage返回以后才运行,此时参数变量已经消失了。若是保留text和count变量会怎样呢?

为了理解这一点,咱们须要对lambda表达式有更深刻的理解。一个lambda表达式包括三个部分:

  • 一段代码
  • 参数
  • 自由变量的值,这里的“自由”指的是那些不是参数而且没有在代码中定义的变量。

在咱们的示例中,lambda表达式有两个自由变量,text和count。数据结构表示lambda表达式必须存储这两个变量的值,即“Hello”和20。咱们能够说,这些值已经被lambda表达式捕获了(这是一个技术实现的细节。例如,你能够将一个lambda表达式转换为一个只含一个方法的对象,这样自由变量的值就会被复制到该对象的实例变量中)。

注意含有自由变量的代码块才被称之为“闭包(closure)”。在Java中,lambda表达式就是闭包。事实上,内部类一直都是闭包。Java8中为闭包赋予了更吸引人的语法

如你所见,lambda表达式能够捕获闭合做用域中的变量值。在java中,为了确保被捕获的值是被良好定义的,须要遵照一个重要的约束。在lambda表达式中,被引用的变量的值不能够被更改。例如,下面这个表达式是不合法的:

public static void repeatMessage(String text,int count){

    Runnable r = () -> {

        while(count > 0){

            count--;        //错误,不能更改已捕获变量的值

            System.out.println(text);

            Thread.yield();

         }

     };

     new Thread(r).start();

}

作出这个约束是有缘由的。更改lambda表达式中的变量不是线程安全的。假设有一系列并发的任务,每一个线程都会更新一个共享的计数器。

int matches = 0;

for(Path p : files)

    new Thread(() -> {if(p中包含某些属性) matches++;}).start();    //非法更改matches的值

若是这段代码是合法的,那么会引发十分糟糕的结果。自增操做matches++不是原子操做,若是多个线程并发执行该自增操做,天晓得会发生什么。

不要期望编译器会捕获全部并发访问错误。不可变的约束只做用在局部变量上,若是matches是一个实例变量或者闭合类的静态变量,那么不会有任何错误被报告出来即便结果一样未定义。一样,改变一个共享对象也是彻底合法的,即便这样并不恰当。例如:

List<Path> matches = new ArrayList<>();

for(Path p: files)

//你能够改变matches的值,可是在多线程下是不安全的

    new Thread(() -> {if(p中包含某些属性) matches.add(p);}).start();

注意matches是“有效final”的(一个有效的final变量被初始化后,就永远不会再被赋一个新值的变量)。在咱们的示例中,matches老是引用同一个ArrayList对象,可是,这个对象是可变的,所以是线程不安全的 。若是多个线程同时调用add方法,结果将没法预测。

lambda表达式的方法体与嵌套代码块有着相同的做用域。所以它也适用一样的命名冲突和屏蔽规则。在lambda表达式中不容许声明一个与局部变量同名的参数或者局部变量。

Path first = Paths.get("/usr/bin");

Comparator<String> comp = (first,second) ->

    Integer.compare(first.length(),second.length());

//错误,变量first已经定义了

在一个方法里,你不能有两个同名的局部变量,所以,你也不能在lambda表达式中引入这样的变量。

当你在lambda表达式中使用this关键字,你会引用建立该lambda表达式的方法的this参数,如下面的代码为例:

public class Application{

    public void doWork(){

        Runnable runner = () -> {....;System.out.println(this.toString());......};

    }

}

表达式this.toString()会调用Application对象的toString()方法,而不是Runnable实例的toString()方法。在lambda表达式中使用this,与在其余地方使用this没有什么不一样。lambda表达式的做用域被嵌套在doWork()方法中,而且不管this位于方法的何处,其意义都是同样的。

 

文章收录,引用自 http://my.oschina.net/fhd/blog/419892

相关文章
相关标签/搜索