理解lambda表达式的Java实现,有两个结构十分关键:第一个是lambda表达式自身,第二个是函数式接口。
lambda表达式本质上就是一个匿名(即未命名)方法。但是,这个方法不是独立执行的,而是用于实现由函数式接口定义的另一个方法。因此,lambda表达式会产生一个匿名类。lambda表达式也常被称为闭包。
函数式接口是仅包含一个抽象方法的接口。这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。例如:标准接口Runnable是一个函数式接口,因为它只定义了一个方法run(),run()定义了Runnable的动作。此外,接口式函数定义了lambda表达式的目标类型。特别注意:lambda表达式只能用于其目标类型已被指定的上下文中。
lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符是-> ,操作符被称为lambda操作符或者是箭头操作符。它将lambda表达式分成两个部分,左侧指定了lambda表达式需要的所有参数(如果不需要参数,则使用空的参数列表“()”),右侧指定了lambda体,即lambda表达式要执行的动作。
Java定义了两种lambda体:一种包含单独一个表达式,另一种包含一个代码块。
看一个简单的lambda表达式的例子,它的计算结果是一个常量值,如下所示:
() -> 123.45
这个lambda表达式没有参数,所以参数列表为空,它返回常量值123.45。因此,这个表达式的作用类似于下面的方法;
double myMath(){
return 123.45;
}
lambda表达式定义的方法没有名称
下面在看一个lambda表达式:
() -> Math.random() * 100
这个lambda表达式使用Math.random()获得一个随机数,将其乘以100,然后返回结果。这个lambda表达式也不需要参数。
当lambda表达式需要参数时,需要在操作符左侧的参数列表中加以指定,下面是一个简单的例子:
(n) -> (n % 2) == 0
如果参数n的值是偶数,这个lambda表达式会返回true。尽管可以显式指定参数的类型,例如本例中的n,但是通常不需要这么做,因为很多时候,参数的类型是可以从上下文中推断出来。与命名方法一样,lambda表达式可以指定需要用到的任意数量的参数。
1.1.2 函数式接口函数式接口是仅指定了一个抽象方法的接口。从JDK8开始,可以为接口声明的方法指定默认行为(默认的方法实现),即所谓的默认方法,只有当没有指定默认实现时,接口的方法才是抽象方法。因为没有指定默认实现的接口方法隐式的是抽象方法,所以没有必要使用abstract修饰符。
下面是函数式接口的一个例子:
public interface MyNumber {
double getValue();
}
在本例中,getValue()方法隐式的是抽象方法,并且是MyNumber定义的唯一方法,所以MyNumber是一个函数式接口,其功能由getValue()定义。
lambda表达式不是独立执行的,而是构成了一个函数式接口定义的抽象方法的实现,该函数式接口定义了它的目标类型,只有在定义了lambda表达式的目标类型的上下文中,才能使用该表达式。当把一个lambda表达式赋给一个函数式接口引用时,就创建了这样的上下文。
下面通过一个例子来说明如何在参数上下文中使用lambda表达式。首先,声明对函数式接口MyNumber的一个引用
MyNumber myNum;
接下来,将一个lambda表达式赋给该接口引用:
myNum = () -> 123.45;
当目标类型上下文中出现lambda表达式时,会自动创建实现了函数式接口的一个类的实例,函数式接口声明的抽象方法的行为由lambda表达式定义。当通过目标调用该方法时,就会执行lambda表达式。因此,lambda表达式提供了一种将代码片段转换为对象的方法。
在前面的例子中,lambda表达式成了getValue()方法的实现,所以,下面的代码将显示123.45:
System.out.println(myNum.getValue()); //结果如下: //123.45
因为赋给myNum的lambda表达式返回值为123.45,所以调用getValue()方法返回的值也是123.45
为了在目标类型上下文中使用lambda表达式,抽象方法的类型和lambda表达式的类型必须兼容。例如:如果抽象方法指定了两个int类型的参数,那么lambda表达式也必须指定两个参数,其类型要么被显式的指定为int类型,要么在上下文中可以隐式的推断为int类型。总的来说,lambda表达式的参数类型和数量必须与方法的参数兼容;返回类型必须兼容;并且lambda表达式可能抛出的异常必须能被方法接受。
package lambda;
interface NumericTest{
boolean test(int n);
}
public class LambdaDemo2 {
public static void main(String[] args) {
NumericTest isEven = (n) -> (n % 2) == 0;
if (isEven.test(10)){
System.out.println("10 是偶数");
}
if (!isEven.test(9)){
System.out.println("9不是偶数");
}
NumericTest isNonNeg = (n) -> n >= 0;
if (isNonNeg.test(1)){
System.out.println("1是正数");
}
if (!isNonNeg.test(-1)){
System.out.println("-1不是正数");
}
}
}
//结果:
//10 是偶数
//9不是偶数
//1是正数
//-1不是正数
这个程序演示了关于lambda表达式的一个重要的地方:函数式接口引用可以用来执行任何与其兼容的lambda表达式。注意,程序中定义了两个不同的lambda表达式,它们都是与函数式接口NumericTest的test()方法兼容,所以都可以通过NumericTest引用执行。
当lambda表达式中有多个参数时,如下所示:
(n,d) -> (n % d) == 0;
两个参数n和d才参数列表中指定,并且用逗号隔开。每当需要一个以上的参数时,就在lambda表达式操作符左侧,使用一个带括号的参数列表指定参数,参数之间使用逗号隔开。
对于lambda表达式中的多个参数,有一点十分重要:如果需要显式声明一个参数类型,那么必须为所有的参数声明类型。例如下面的代码是合法的:
(int n,int d) -> (n % d) == 0;
这是不合法的:
(int n, d) -> (n % d) == 0;1.2 块lambda表达式
块lambda表达式是操作符的右侧的代码不是一个简单的表达式,而是由一个代码块组成,其中包含多条语句。这种类型的lambda体被称为块体,具有块体的lambda表达式被称为块lambda。
块lambda扩展了lambda表达式内部可以处理的操作类型,因为它允许lambda体包含多条语句。例如,在块lambda中可以声明变量、使用循环、指定if和switch语句、创建嵌套代码块等。创建块lambda只需要使用花括号包围lambda体,就像创建其他语句块一样。
在块lambda中重要一点是:在块lambda中必须显式使用return语句来返回值。因为块lambda体代表的不是单独一个表达式。
下面这个例子使用块lambda来计算并返回一个int类型值的阶乘:
package lambda;
interface NumuricFunc{
int func(int n);
}
public class BlockLambdaDemo {
public static void main(String[] args) {
NumuricFunc factorial = (n) -> {
int result = 1;
for (int i = 1;i <= n;i++){
result *= i;
}
return result;
};
System.out.println(factorial.func(3));
System.out.println(factorial.func(6));
}
}
//结果如下:
//6
//720
在程序中,块lambda声明了变量result,使用了for循环,有一条return语句。在lambda表达式中出现return语句时,只是从lambda体返回,而不会导致包围lambda提的方法返回。



