前面的文章中我们有提到,Lambda表达式是函数式接口的一个实例,然而,我们并没有从Lambda表达式中看到有关函数式接口的任何信息,因而有必要弄清楚,Lambda的类型是什么?它实现了哪个函数式接口?
其实,Lambda表达式的类型是从上下文推断出来的,这里的上下文包括如下3种:
- 赋值上下文
- 方法调用上下文(参数与返回值)
- 类型转换上下文
通过这3种上下文就可以推断出Lambda表达式的目标类型(与之对应的函数式接口)。
下面来看几个示例:
- 示例1
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
filter(List< Apple > inventory, Predicate< Apple > p)
的调用推断出Lambda表达式(Apple a) -> a.getWeight() > 15
的目标类型是Predicate< Apple >,这里,p的抽象方法test接受apple,返回布尔值,与lambda表达式的函数描述符Apple -> boolean相匹配,因此代码的类型检查无误。 - 示例2:
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
类型推断是指不需要指定lambda表达式参数的类型,它可以从lambda目标类型中推断出来,使我们的lambda表达式更为简洁。
下面代码中,参数a就没有显式的指定其类型为Apple,它可以从上下文中推断而来。
List<Apple> greenApples = filter(inventory, a -> "green".equals(a.getColor()));
在Lambda表达式中,也可以使用局部变量,就像匿名内部类在使用局部变量时需要用final修饰一样,Lambda表达式也有这个限制。像下面的代码(错误代码):
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
错误原因:Lambda表达式引用的局部变量必须是final变量或事实上的final变量,这里portNumber被再次修改了,显示不是final类型。
为什么会有这个限制?
局部变量保存在栈上,如果Lambda是在一个子线程中使用局部变量,则使用Lambda的线程,可能会在分配该变量的主线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量是final类型,仅仅赋值一次,那就无所谓了,因此就有了这个限制。