Java中的lambda表达式

为什么引入lambda表达式

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

考虑如何用一个定制比较器完成排序。如果想按长度而不是默认的字典顺序对字符串进行排序,可以向sort方法传入一个Comparator对象:

class LengthComparator implements Comparator<String> {
	public int compare(String first, String second) {
		return first.length() - second.length();
	}
}
...
Arrays.sort(strings,new LengthComparator());

compare方法不是立即调用。实际上,在数组完成排序之前,sort方法会一直调用compare方法,只要元素的顺序不正确就会重新排列元素。将比较元素所需的代码段放在sort方法中,这个代码将与其余的排序逻辑集成。

这个例子是将一个代码块传递到某个对象(一个sort方法)。这个代码块会在将来某个时间调用。到目前为止,在Java中传递一个代码段并不容易,你不能直接传递代码段。Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。

在其他语言中,可以直接处理代码块。Java设计者很长时间以来一直拒绝增加这个特性。毕竟,Java的强大之处就在于其简单性和一致性。倘若只要一个特性能够让代码稍简洁一些,就把这个特性增加到语言中,那么这个语言很快就会变得一团糟,无法管理。

lambda表达式的语法

已排序为例。可以传入代码来检查一个字符串是否比另一个字符串短。这里要计算:

first.length() - second.length()

first和second都是字符串。Java是一种强类型语言,所以要指定类型:

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

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

以上是Java中的一种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();

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

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

实例:

    public static void main(String[] args) {
        String[] x = {"dsadsdas","abc","bcd","asda","adsad"};
        Arrays.sort(x,(String first,String second)->(first.length() - second.length()));
        System.out.println(Arrays.toString(x));
    }

函数式接口

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

为了展示如何转换为函数式接口,考虑Arrays.sort方法。它的第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式:

Arrays.sort(words,(first,second)->first.length()-second.length());

在底层,Arrays.sort方法会接收实现了Comparator<String>的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。这些对象和类的管理完全取决于具体实现,与使用传统的内联类相比,这样可能要高效得多。最好把lambda表达式看作是一个函数,而不是一个对象,另外要接受lambda表达式可以传递到函数式接口。

lambda表达式可以转换为接口,这一点让lambda表达式很有吸引力。

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

甚至不能把lambda表达式赋给类型为Object的变量,Object不是函数式接口。

方法引用

有时,lambda表达式涉及一个方法。
假设要对字符串进行排序,而不考虑字母的大小写。可以传递以下方法表达式:

Arrays.sort(String,String::compareToIgnoreCase);

表达式String::compareToIgnoreCase是一个方法引用(method reference),它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。

类似于lambda表达式,方法引用也不是一个对象。不过,为一个类型为函数式接口的变量赋值时会生成一个对象。

从上例看出,要用::运算符分隔方法名与对象或类名。主要有3种情况:

  • object::instanceMethod
  • Class::instanceMethod
  • Class::staticMethod

在第1种情况下,方法引用等价于向方法传递参数的lambda表达式。对于第2种情况,第1个参数会成为方法的隐式参数。例如,String::compareToIgnoreCase等同于(x,y)->x.compareToIgnoreCase(y)。在第3种情况下,所有参数都传递到静态方法:Math::pow等价于(x,y)->Math.pow(x,y)。

注意,只有当lambda表达式的体只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。考虑以下lambda表达式:

s -> s.length() == 0

这里有一个方法调用。但是还有一个比较,所以这里不能使用方法引用。

如果有多个同名的重载方法,编译器就会尝试从上下文中找出你指的是哪一个方法。例如,Math.max方法有两个版本,一个用于整数,另一个用于double值。选择哪一个版本取决于Math::max转换为哪个函数式接口的方法参数。类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。

构造器引用

构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。选择哪个构造器取决于上下文。

变量作用域

lambda表达式有3个部分:

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

lambda表达式中可以访问外围方法或类中的变量。
lambda表达式的数据结构必须存储自由变量的值,可以说它被lambda表达式捕获(captured)了。lambda表达式可以捕获外围作用域中变量的值。在Java中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在lambda表达式中,只能引用值不会改变的变量。这个限制是有原因的。如果在lambda表达式中更改变量,并发执行多个动作时就会不安全。另外如果在lambda表达式中引用一个变量,而这个变量可能在外部改变,这也是不合法的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值