文档章节

java8之lambda表达式(变量作用域)

柳哥
 柳哥
发布于 2015/05/25 23:34
字数 1195
阅读 7740
收藏 77

通常,我们希望能够在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参数,以下面的代码为例:

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位于方法的何处,其意义都是一样的。

© 著作权归作者所有

柳哥
粉丝 207
博文 405
码字总数 347782
作品 0
杭州
技术主管
私信 提问
加载中

评论(15)

lanmingle
lanmingle
和一般匿名区别,我注意的是,自身是指向自身的类,我觉得这用起来很舒服。再也不用类.this.xxx
柳哥
柳哥 博主

引用来自“Xiao_f”的评论

可以说JAVA已经被改的人不人鬼不鬼了,加入过多的特性,百害无一利: (first,second) ->,对于这种可以不写形参的特性只能说颠覆了java坚守的理念,从代码一眼看不出类型不如去玩脚本了,java你还有什么优势?官方回应不支持重载运算符的理由不是不方便阅读吗,这次咋打自己嘴了,想赶时髦又放不下向下兼容,语法只能越来越奇葩

引用来自“柳哥”的评论

这是向函数式靠拢好不!?

引用来自“Xiao_f”的评论

我要表达的真是意义是,如果想靠拢就彻底点、干脆点,要不就别搞这些不完善的半成品特性,坚持好自己的理念、保持好自己的风格就行了,你见哪个语言支持闭包又不支持修改局部变量的,多线程下出问题只是一个借口,其本质是jvm从设计的时候就没考虑这方面的特性,为了向下兼容又无法推翻已有的架构。面对java的语法诟病,javaer早已不在乎多敲几个字母、多写几个类了,重要的是你革新就拿出决心来,要不就别憋个好几年出个不伦不类、可用性极差的功能是不?至少现在我不相信现在哪个公司生产环境敢直接上java8,并用java8特性的。
你说的也可能是对的,java毕竟历史包袱重,又要向下兼容,所以改起来不那么容易。scala,clojure没有这个包袱!
X
Xiao_f

引用来自“Xiao_f”的评论

可以说JAVA已经被改的人不人鬼不鬼了,加入过多的特性,百害无一利: (first,second) ->,对于这种可以不写形参的特性只能说颠覆了java坚守的理念,从代码一眼看不出类型不如去玩脚本了,java你还有什么优势?官方回应不支持重载运算符的理由不是不方便阅读吗,这次咋打自己嘴了,想赶时髦又放不下向下兼容,语法只能越来越奇葩

引用来自“柳哥”的评论

这是向函数式靠拢好不!?
我要表达的真是意义是,如果想靠拢就彻底点、干脆点,要不就别搞这些不完善的半成品特性,坚持好自己的理念、保持好自己的风格就行了,你见哪个语言支持闭包又不支持修改局部变量的,多线程下出问题只是一个借口,其本质是jvm从设计的时候就没考虑这方面的特性,为了向下兼容又无法推翻已有的架构。面对java的语法诟病,javaer早已不在乎多敲几个字母、多写几个类了,重要的是你革新就拿出决心来,要不就别憋个好几年出个不伦不类、可用性极差的功能是不?至少现在我不相信现在哪个公司生产环境敢直接上java8,并用java8特性的。
柳哥
柳哥 博主

引用来自“Xiao_f”的评论

可以说JAVA已经被改的人不人鬼不鬼了,加入过多的特性,百害无一利: (first,second) ->,对于这种可以不写形参的特性只能说颠覆了java坚守的理念,从代码一眼看不出类型不如去玩脚本了,java你还有什么优势?官方回应不支持重载运算符的理由不是不方便阅读吗,这次咋打自己嘴了,想赶时髦又放不下向下兼容,语法只能越来越奇葩
这是向函数式靠拢好不!?
X
Xiao_f
可以说JAVA已经被改的人不人鬼不鬼了,加入过多的特性,百害无一利: (first,second) ->,对于这种可以不写形参的特性只能说颠覆了java坚守的理念,从代码一眼看不出类型不如去玩脚本了,java你还有什么优势?官方回应不支持重载运算符的理由不是不方便阅读吗,这次咋打自己嘴了,想赶时髦又放不下向下兼容,语法只能越来越奇葩
柳哥
柳哥 博主

引用来自“烟头”的评论

内部匿名类是声明为final,而lambda不需要声明为final,但实际是final的,只要理解内部匿名类就可以理解了
烟头
烟头
内部匿名类是声明为final,而lambda不需要声明为final,但实际是final的,只要理解内部匿名类就可以理解了
柳哥
柳哥 博主

引用来自“LC”的评论

"在lambda表达式中,被引用的变量的值不可以被更改" 类似于匿名内部类只能接受final类型的参数一样吧
柳哥
柳哥 博主

引用来自“抓瓦工人”的评论

和内部类引用外部类的变量方式一样,需要final化参数,否则,一旦外部更改就会有不一致的情况不可预料的发生
柳哥
柳哥 博主

引用来自“gen23167”的评论

作者应该是lamda, 不是lambda
lambda吧?
Java 8 Lambda 表达式示例

自从我听说Java8将要支持Lambda表达式(或称闭包),我便开始狂热的想要将这些体面的简洁的功能元素应用到我的代码中来。大多开发者普遍的使用匿名内部类来开发事件处理器,比较器,thread/...

oschina
2014/05/11
10.6K
52
[Kotlin] 关于lambda,你想知道的都在这里

从Java语言转到Kotlin,最让人头疼的问题恐怕就是lambda表达式了。 lambda,准确的中文翻译是:匿名函数。 不过,在Kotlin语言中本身就有匿名函数的概念,为了区分,我们姑且把它叫做Lambda表...

欧阳锋
2017/11/26
0
0
Java Lambda 表达式使用

Java8 引入了 Lambda 表达式,使用 Lambda 表达式可以让代码更加简洁。Lambda 表达式其实也就是一个匿名函数,我们可以用它去代替匿名函数,我们先来看一个例子 1、Lambda 表达式语法 我们用...

非摩尔根
01/15
58
0
Java 8 和 Scala 中的 Lambda 表达式

这篇文章是infoQ上面关于java 8 和 scala 特性比较的一部分,原文实在是太长了,,分段翻译,先贴出来一部分,翻译的不好,欢迎拍砖。。。原文地址 Java8 终于要支持Lambda表达式!自2009年以...

散装海盗
2012/07/11
2.1K
0
Java8 Lambda表达式

函数式接口 理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论。FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那...

mifans
2016/11/02
82
0

没有更多内容

加载失败,请刷新页面

加载更多

Qt程序打包发布方法(使用官方提供的windeployqt工具)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/toTheUnknown/article/details/81748179 如果使用到了Qt ...

shzwork
29分钟前
4
0
MainThreadSupport

MainThreadSupport EventBus 3.0 中的代码片段. org.greenrobot.eventbus.MainThreadSupport 定义一个接口,并给出默认实现类. 调用者可以在EventBus的构建者中替换该实现. public interface ...

马湖村第九后羿
50分钟前
3
0
指定要使用的形状来代替文字的显示

控制手机键盘弹出的功能只能在ios上实现,安卓是实现不了的,所以安卓只能使用type类型来控制键盘类型,例如你要弹出数字键盘就使用type="number",如果要弹出电话键盘就使用type="tel",但这...

前端老手
今天
6
0
总结:Raft协议

一、Raft协议是什么? 分布式一致性算法。即解决分布式系统中各个副本数据一致性问题。 二、Raft的日志广播过程 发送日志到所有Followers(Raft中将非Leader节点称为Follower)。 Followers收...

浮躁的码农
今天
7
0
Flask-admin Model View字段介绍

Model View字段介绍 can_create = True 是否可以创建can_edit = True 是否可以编辑can_delete = True 是否可以删除list_template = 'admin/model/list.html' 修改显......

dillonxiao
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部