Java 8 之 lambda 变量作用域

通常,我们希望能够在lambda表达式的闭合方法或类中访问其他的变量,例如:

package java8test;

public class T1 {
    public static void main(String[] args) {
        repeatMessage("Hello", 20);
    }
    public static void repeatMessage(String text,int count){
        Runnable r = () -> {
            for(int i = 0; i < count; i++){
                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参数,以下面的代码为例:
```javascript
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://www.oschina.net]

时间: 2024-07-29 16:33:53

Java 8 之 lambda 变量作用域的相关文章

Java的方法重载与变量作用域简介_java

方法的重载上面使用的max方法仅仅适用于int型数据.但如果你想得到两个浮点类型数据的最大值呢? 解决方法是创建另一个有相同名字但参数不同的方法,如下面代码所示: public static double max(double num1, double num2) { if (num1 > num2) return num1; else return num2; } 如果你调用max方法时传递的是int型参数,则 int型参数的max方法就会被调用: 如果传递的事double型参数,则doubl

[JAVA]变量作用域的问题

变量|问题 变量作用域的问题:public String getAnswer() {  String myAnswer;  try  {      BufferedReader bfReader=new BufferedReader(new InputStreamReader(System.in));      myAnswer=new String(bfReader.readLine().toString());   System.out.println("你输入的是:"+myAns

Java与C++语言在作用域上的差异浅析

Java与C++的编程思想虽然有一定的共同性,但是在很多方面仍然存在着不同 .如两者在作用域上仍然存在着很大的差异.下面笔者就分析一下这两门语言在 作用域上的差异,这里所提到的作用域包括变量作用域和对象作用域. 差异一:变量作用域的不同 如下面这段程序代码是符合C++语言的语法要求的.其可以在C语言下正常运 行.但是其在Java语言平台下编译的时候,就会被告知有错误.其格式.关键字 上面都没有错误.那么错误到底是这么呢?这就关系到变量的作用域. { float y=3.15 { float y=

Javascript变量作用域详解

         这篇文章主要是对Javascript变量作用域进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助 变量的作用域指的是变量的可见性,而生命周期则(存活期)则是从另一个角度考察变量.         JS中变量的作用域分为全局变量和局部变量,函数内定义的称为局部变量,函数外的称为全局变量.("函数外的称为全局变量"是相对的,另此处讨论的前提是用var显式声明的变量,函数内不用var定义的变量默认是全局变量,当然忽略var声明变量是不赞成的).    代码如

JAVA学习(三):Java基础语法(变量、常量、数据类型、运算符与数据类型转换)

Java基础语法(变量.常量.数据类型.运算符与数据类型转换) 1.变量 Java中,用户可以通过指定数据类型和标识符来声明变量,其基本语法为: DataType identifier;  或  DataType identifier = value; 其中,DataType是变量类型,如int/string/char/double/boolean等:identifier是变量名称,即标识符:value就是声明变量的值. 注: a.标识符由数字0-9.大小写字母.下划线.美元符号.人民币符号以及

java中类定义的变量和类方法里面定义的变量有什么区别和联系

问题描述 java中类定义的变量和类方法里面定义的变量有什么区别和联系 java中类定义的变量和类方法里面定义的变量有什么区别和联系为什么要再方法当中定义变量,统一在类里面,方法外面定义变量不是更方便快捷么 解决方案 Java中的作用域非常容易理解就看大括号.一个变量的作用域始终是从定义的位置开始直到当前大括号结束.在这个范围之内就可以不使用任何前缀直接访问它.不在这个范围内那就必须得""想办法""访问它. 解决方案二: 类种定义的变量是类的成员变量,在所有的类方法

Python基本语法_变量作用域LEGB

目录 目录 软件系统 变量的作用域 高级语言对数据类型的使用过程 作用域的产生 作用域的类型 Llocal局部作用域 Eenclosing嵌套作用域 Gglobal全局作用域 Bbuilt-in内置作用域 变量名解析LEGB法则 实例说明 对变量的引用 对变量的修改 global关键字 nonlocal关键字 命名空间和作用域的区别 软件系统 系统 Ubuntu 14.04 软件 Python 2.7.3 IPython 4.0.0 变量的作用域 在Python程序中创建.改变.查找变量名时,都

Java 8里面lambda的最佳实践

Java 8已经推出一段时间了,越来越多开发人员选择升级JDK,这条热门动弹里面看出,JDK7最多,其次是6和8,这是好事! 在8 里面Lambda是最火的主题,不仅仅是因为语法的改变,更重要的是带来了函数式编程的思想,我觉得优秀的程序员,有必要学习一下函数式编程的思想以开阔思路.所以这篇文章聊聊Lambda的应用场景,性能,也会提及下不好的一面. Java为何需要Lambda 1996年1月,Java 1.0发布了,此后计算机编程领域发生了翻天覆地的变化.商业发展需要更复杂的应用,大多数程序都

在Java中使用Lambda表达式的技巧

在本文中,我们将展示一些在 Java 8 中不太为人所了解的 Lambda 表达式技巧及其使用限制.本文的主要的受众是 Java 开发人员,研究人员以及工具库的编写人员. 这里我们只会使用没有 com.sun 或其他内部类的公共 Java API,如此代码就可以在不同的 JVM 实现之间进行移植. 快速介绍 Lambda 表达式作为在 Java 8 中实现匿名方法的一种途径而被引入,可以在某些场景中作为匿名类的替代方案. 在字节码的层面上来看,Lambda 表达式被替换成了 invokedyna