【lambda表达式】变量作用域和lambda 表达式的处理

变量作用域

通常, 你可能希望能够在 lambda 表达式中访问外围方法或类中的变量。

public static void repeatMessage(String text, int delay){
    ActionListener listener = event ->{ 
        System.out.println(text);
        Toolkit.getDefaultToolkitO.beep();
    }
    new Timer(delay, listener).start0;
}

注意 lambda 表达式中的变量text并不是在这个 lambda 表达式中定义的。实际上,这是 repeatMessage 方法的一个参数变量。

想想看, 这里好像会有问题, 尽管不那么明显。lambda 表达式的代码可能会在 repeatMessage 调用返回很久以后才运行,而那时这个参数变量已经不存在了。如何保留 text 变量呢?

要了解到底会发生什么,下面来巩固我们对 lambda 表达式的理解。lambda 表达式有 3 个部分:

  1. 一个代码块;
  2. 参数;
  3. 自由变量的值, 这是指非参数而且不在代码中定义的变量。

在例子中, 这个 lambda 表达式有 1 个自由变量 text。 表示 lambda 表达式的数据结构必须存储自由变量的值,在这里就是字符串 "Hello" 。我们说它被 lambda 表达式捕获 (下面来看具体的实现细节。 例如,可以把一个 lambda 表达式转换为包含一个方法的对象,这样自由变量的值就会复制到这个对象的实例变量中。)

关于代码块以及自由变量值有一个术语: 闭包(closure) 。如果有人吹嘘他们的语言有闭包,现在你也可以自信地说 Java 也有闭包。在 Java 中, lambda 表达式就是闭包。

可以看到,lambda 表达式可以捕获外围作用域中变量的值。 在 Java 中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在 lambda 表达式中, 只能引用值不会改变的变量。

之所以有这个限制是有原因的。如果在 lambda 表达式中改变变量, 并发执行多个动作时就会不安全。对于目前为止我们看到的动作不会发生这种情况,不过一般来讲,这确实是 一个严重的问题。 另外如果在 lambda 表达式中引用变量, 而这个变量可能在外部改变,这也是不合法的。

这里有一条规则:lambda 表达式中捕获的变量必须实际上是最终变量 ( effectively final)。 实际上的最终变量是指, 这个变量初始化之后就不会再为它赋新值。在这里,text 总是指示 同一个 String 对象,所以捕获这个变量是合法的。

lambda 表达式的体与嵌套块有相同的作用域。这里同样适用命名冲突和遮蔽的有关规则。在 lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。

public class Application{
    public void init(){
        ActionListener listener = event ->{
            System.out.println(this.toString());
            ...
        }
        ...
    }
}

表达式 this.toString() 会调用 Application 对象的 toString方法, 而不是 ActionListener 实例的方法。在 lambda 表达式中, this 的使用并没有任何特殊之处。lambda 表达式的作用域嵌套在 init 方法中,与出现在这个方法中的其他位置一样, lambda 表达式中 this 的含义并没有变化。

lambda 表达式的处理

到目前为止, 你已经了解了如何生成 lambda 表达式, 以及如何把 lambda 表达式传递到 需要一个函数式接口的方法。下面来看如何编写方法处理 lambda 表达式。

使用 lambda 表达式的重点是延迟执行 ( deferred execution )。 毕竟, 如果想要立即执行代码,完全可以直接执行, 而无需把它包装在一个lambda 表达式中。之所以希望以后再执行代码, 这有很多原因, 如:

  • 在一个单独的线程中运行代码;
  • 多次运行代码;
  • 在算法的适当位置运行代码(例如,排序中的比较操作;)
  • 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等;)
  • 只在必要时才运行代码。

下面来看一个简单的例子。假设你想要重复一个动作 n 次。 将这个动作和重复次数传递到一个 repeat 方法:

repeat(10, 0 -> System.out.println("Hello, World!")) ;

要接受这个 lambda 表达式, 需要选择(偶尔可能需要提供)一个函数式接口。 表 6-1 列出了 Java API 中提供的最重要的函数式接口。在这里, 我们可以使用 Runnable 接口:

public static void repeat(int n, Runnable action) { for (int i = 0; i < n; i++) action.run(); }

需要说明的是,调用 action.run() 时会执行这个 lambda 表达式的主体。

image

现在让这个例子更复杂一些。我们希望告诉这个动作它出现在哪一次迭代中。 为此,需要选择一个合适的函数式接口,其中要包含一个方法, 这个方法有一个 int 参数而且返回类型为 void。处理 int 值的标准接口如下:

public interface IntConsumer{
    void accept(int value);
}

下面给出 repeat 方法的改进版本:

public static void repeat(int n, IntConsumer action){
    for (int i = 0; i < n; i++) action.accept(i);
}

可以如下调用它:

repeat(10, i -> System.out.println(" Countdown: " + (9 - i)));

表 6-2 列出了基本类型 int 、long 和 double 的 34 个可能的规范。 最好使用这些特殊化规范来减少自动装箱。出于这个原因, 我在上一节的例子中使用了 IntConsumer 而不是 Consume<lnteger>

image

最好使用表 6-1 或表 6-2 中的接口。 例如,假设要编写一个方法来处理满足某个特定条件的文件。 对此有一个遗留接口 java.io.FileFilter, 不过最好使用标准的 Predicate , 只有一种情况下可以不这么做, 那就是你已经有很多有用的方法可以生成 FileFilter 实例。

大多数标准函数式接口都提供了非抽象方法来生成或合并函数。 例如,Predicate. isEqual(a)等同于 a::equals, 不过如果 a 为 null 也能正常工作。已经提供了默认方法 and 、or 和 negate 来合并谓词。 例如, Predicate.isEqual(a).or(Predicate.isEqual(b)) 就等同于 x -> a.equals(x) || b.equals(x)

如果设计你自己的接口,其中只有一个抽象方法,可以用 @FunctionalInterface 注解来标记这个接口。这样做有两个优点。 如果你无意中增加了另一个非抽象方法, 编译器会产生一个错误消息。 另外 javadoc 页里会指出你的接口是一个函数式接口。

并不是必须使用注解根据定义,任何有一个抽象方法的接口都是函数式接口。不过使用 @FunctionalInterface 注解确实是一个很好的做法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容