Java学习笔记(十五)——lambda表达式(上)

lambda 表达式就是一个代码块,以及必须传入代码的变量规范。

我们来看一下 lambda 表达式形式:参数,箭头 ( -> ) 以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在 {} 中,并包含显示的 return 语句。例如:

(String first,String second) -> {
    if(first.length() < second.length())
        return -1;
    else if(first.length() > second.length())
        return 1;
    else
        return 0;
}

即使 lambda 表达式没有参数,仍然要提供空括号,就像无参数方法一样:

() -> {
    for(int i = 100; i >= 0; i--)
        System.out.println(i);
}

如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。例如:

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

在这里,编译器可以推导出 first 和 second 必然是字符串,因为这个 lambda 表达式将赋给一个字符串比较器。

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至可以省略小括号:

ActionListener listener
    = event ->
        System.out.println("The time is" + new Date());

无需指定 lambda 表达式的返回类型。lambda 表达式的返回类型总是会由上下文推导得出。例如,下面的表达式

(String first, String second) ->
    first.length() - second.length();

可以在需要 int 类型结果的上下文中使用。

注释: 如果一个 lambda 表达式只在某些分支返回一个值,而在另一些分支上不返回值,这是不合法的。例如,(int x) -> {if(x >= 0) return 1;}就不合法。

下面的代码显示了在一个比较器和一个动作监听器中使用 lambda 表达式。

package lambdatest;

import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

/**
 * This program demonstrates the use of lambda expressions
 * @version 1.8 2018-2-19
 * @author ShenXueYan
 */

public class LambdaTest {

    public static void main(String[] args) {

        String[] planets = new String[] {"Mercury", "Venus", "Earch", "Mars",
                "Jupiter", "Saturn", "Uranus", "Neptune"};

        System.out.println(Arrays.toString(planets));

        System.out.println("Sorted in dictionary order: ");
        Arrays.sort(planets);
        System.out.println(Arrays.toString(planets));

        System.out.println("Sorted by length: ");
        Arrays.sort(planets, (first, second) -> first.length() - second.length());
        System.out.println(Arrays.toString(planets));

        Timer t = new Timer(1000, event -> 
                System.out.println("The time is " + new Date()));
        t.start();

        //keep program running until user selects "OK"
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);

    }

}

运行结果截图:
这里写图片描述

这里写图片描述

前面已经讨论过,Java 中已经有很多封装代码块的接口,如 ActionListener 或 Comparator。lambda 表达式与这些接口是兼容的。

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为 函数式接口(functional interface)。

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

实际上,在 Java 中,对 lambda 表达式所能做的也只是能转换为函数式接口。

Java API 在 java.util.function 包中定义了很多非常通用的函数式接口。例如,java.util.function 包有一个尤其有用的接口 Predicate:

public interface Predicate<T> {
    boolean test(T t);
    // Additional default and static methods

ArrayList 类有一个 removeIf 方法,它的参数就是一个 Predicate。这个接口专门用来传递 lambda 表达式。例如,下面的语句将从一个数组列表删除所有的 null 值:

list.removeIf(e -> e == null)

让我们用旧方法和 lambda 方法做比较

先来定义一个函数式接口

@FunctionalInterface
public interface ActionListener{
    void actionPerformed(ActionEvent event);
}

下面开始对比:

// 传统方法
public class TimerTest {
    public static void main(String[] args){
        ActionListener listener = new TimePrinter();

        Timer t = new Timer(10000,listener);
        t.start();
        ...
    }
}

class TimePrinter implements ActionListener {
    public void actionPerformed(ActionEvent event) {
        System.out.println("At the tone, the time is " + new Date();
    }
}
// lambda 表达式方法
public class TimerTest {
    public static void main(String[] args){
        Timer t = new Timer(10000, event -> {
            System.out.println("At the tone, the time is " + new Date());
        });
        t.start();
        ...
    }
}

真的有点简单粗暴啊 捂脸

在Lambda表达式使用中,Lambda表达式外面的局部变量会被JVM隐式的编译成final类型,Lambda表达式内部只能访问,不能修改 。

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

在 lambda 表达式中,只能引用值不会改变的变量。例如,下面的做法是不合法的:

public static void countDown(int start, int delay) {
    ActionListener listener = event -> {
        start--;
        System.Timer(delay, listener).start();
    };
    new Timer(delay, listener).start();
}

另外如果在 lambda 表达式中引用变量,而这个变量可能在外部改变,这也是不合法的。例如,下面就是不合法的:

public static void repeat(String text, int count) {
    for(int i = 1; i <= count; i++) {
        ActionListener listener = event -> {
            System.out.println(i + ":" + text);
            // Error: Cannot refer to changing i
        }
    new Timer(1000, listener).start();
    }
}

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

在 lambda 表达式中使用 this 关键字时,是指创建这个 lambda 表达式的方法的 this 参数。例如,考虑下面的代码:

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 的含义并没有变化。

……

欲听后事如何,请听下回分解


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值