lambda 表达式

lambda 表达式

为什么引入 lambda 表达式

lambda 表达式是一个可传递的代码块,可以在以后执行一次或多次

两个常见例子

  • 按指定时间间隔完成工作
  • 用一个定制比较器完成排序(比如按长度而不是默认的字典顺序对字符串排序)。

两个例子的共同点:

  • 都是将一个代码块传递到某个对象(一个定时器,或者一个 sort 方法)。
  • 这个代码块在将来某个时间调用。

到目前为主,在java 中传递一个代码段并不容易,你不能直接传递代码段. Java 是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。

lambda 表达式语法

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

  • 即使 lambda 表达式没有参数,仍然要提供空括号,就像无参数方法一样
() -> {for (int i = 100; i >= 0; i--) System.out.println(i);}
  • 若可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。
Comparator<String> comp
	= (first, second) // same as (String first, String second)
		-> first.length() - second.length();

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

  • 若方法只有一个参数,而且这个参数的类型可以推导得出,则可以省略小括号
ActionListener listener = event ->
	System.out.println("The time is "
		+ Instant.ofEpochMilli(event.getWhen()));
  • 无需指定 lambda 表达式的返回类型。 lambda 表达式的返回类型总是会由上下文推导得出。例如:
(String first, String second) -> first.length() - second.length()

函数式接口

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

方法引用

例子,假设你希望只要出现一个定时器时间就打印这个事件对象。
可以这样写:

var timer = new Timer(1000, event -> System.out.println(event));

但若直接把 println 方法传递到 Timer 构造器就更好了。具体做法如下:

var timer = new Timer(1000, Symtem.out::println);

表达式 Symtem.out::println 是一个方法引用:它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。

在这个例子中,会生成了一个 ActionListener,它的 actionPerformed(ActionEvent e) 方法要调用 System.out.println(e).

构造器引用

  • 与方法引用类似,只不过方法名为 new。例如: Person::new
  • 可以用数组类型建立构造器引用。

    例如,int[]::new 是一个构造器引用,它有一个参数:即数组的长度。等价于 lambda 表达式 x -> new int[x]

  • 数组构造器引用能够克服Java 无法构造泛型类型 T 的数组的限制

    Person[] people = stream.toArray(Person[]::new);
    toArray方法调用这个构造器来得到一个有正确类型的数组,然后填充并返回这个数组

变量作用域

lambda 表达式有 3 个部分:

  • 一个代码块
  • 参数
  • 自由变量的值,这是指非参数而且不在代码中定义的变量

注意:

代码块以及自由变量值有一个术语:闭包(closure).
在java中,lambda 表达式就是一个闭包

  • lambda 表达式可以捕获外围作用域变量的值。但是只能引用不会改变的变量。原因在于,若出现更改变量,并发执行多个动作时就会不安全。

  • 若在 lambda 表达式中引用一个变量,而这个变量可能在外部改变,这也是不合法的。

    规则:
    lambda 表达式中捕获的变量必须是实际上是事实最终变量(effectively final).
    事实最终变量是指:这个变量初始化后,就不会再为它赋新值。

  • 在lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。

  • 在一个 lambda 表达式中使用 this 关键字,是指创建这个 lambda 表达式的方法的 this 参数。

处理lambda表达式

使用 lambda 表达式的重点是延迟执行,原因在于:

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

再谈 Comparator

Comparator 接口包含很多方便的静态方法来创建比较器。这些方法可以用于 lambda 表达式或方法引用。

静态 comparing 方法取一个 “键提取器”函数,它将类型 T 映射为一个可比较的类型(如:String).对要比较的对象应用这个函数,然后对返回的键完成比较。

例如,假设有一个 Person 对象数组,可以如下按名字对这些对象进行排序:

Arrays.sort(people, Compatator.comparing(Person::getName));

相比手动实现一个 Comparator,这要容易的多。而且代码更为清晰,因为显然我们都希望按人名来进行比较。

可以把比较器和 thenComparing 方法串起来,来处理比较结果相同的情况。例如:

Arrays.sort(people,
	Compatator.comparing(Person::getLastName)
	.thenComparing(Person::getFirstName));

如果两个人的姓相同,就会使用第二个比较器。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值