1. 引言
我希望通过这一篇文章、可以让读者全面了解Lambda表达式、也许不够全面、我在尽力完善它、也希望你能留下宝贵意见、在下方留言。文章有点长、请耐心看完。
2. 描述
- 可以将 Lambda 表达式理解为简洁的表示可传递匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可抛出的异常列表。
- Lambda 表达式是实现行为参数化的一种方式,这种方式比起使用匿名内部类的方式更加的简洁、易读。
3. 组成
- 参数
- 箭头
- 主体
例如
1
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
4. Lambda 语法
- (parameters) -> expression
- (parameters) -> { statements; }
5.有效的 Lambda 表达式
1 | /* 表示有一个 String 类型的入参,且返回一个 int 类型的结果。 */ |
6. 使用 Lambda 表达式
6.1 函数式接口
- 定义:只定义了一个抽象方法的接口称为函数式接口。接口中可以包含多个 default 方法,只要接口只定义了一个抽象方法、那么该接口就是函数式接口,比如java.lang.Runnable 类和 java.util.Comparator 类。
- 作用:Lambda 表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,且将整个表达式作为函数式接口的实例(具体来说、Lambda表达式是函数式接口的一个实例)。
- 备注:可以使用 @FunctionalInterface 注解注明接口是函数式接口,提高代码的可读性。若接口包含多个抽象方法(非函数式接口),使用该注解将会编译报错。
示例
1 | public static void main(String[] args) { |
结论: 函数式接口通过使用 Lambda 表达式的方式创建接口的一个实例,比通过匿名类的方式,代码更加的简洁大方。
6.2 常用函数式接口
Java API中已经有几个函数式接口,如Comparable、Runnable、Callable
6.2.1 Predicate
java.util.function.Predicate
1 | /** |
注意: Stream.filter()方法接收一个Predicate
1 | public interface Stream<T> extends BaseStream<T, Stream<T>> { |
6.2.2 Consumer
java.util.function.Consumer
1 | public static void main(String[] args) { |
6.2.3 Function
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个
泛型T的对象,并返回一个泛型R的对象。
1 | public static void main(String[] args) { |
注意
任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要 Lambda 表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda 包在一个try/catch块中。
7. 类型检查、类型推断及限制
7.1 类型检查
Lambda 类型是从使用 Lambda 的上下文推断出来的,上下文中 Lambda 表达式需要的类型成为目标类型。

7.2 同样的 Lambda , 不同的函数式接口
有了目标类型的概念,同一个 Lambda 表达式就可以与不同的函数式接口联系起来,只要他们的抽象方法能够兼容。
举例
1 | Callable<Integer> c = () -> 42; |
特殊的void兼容规则
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当
然需要参数列表也兼容)
举例
1 | // Predicate返回了一个boolean |
7.3 类型推断
你还可以进一步简化你的代码。Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合 Lambda 表达式,这意味着它也可以推断出适合 Lambda 的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解 Lambda 表达式的参数类型,这样就可以在 Lambda 语法中省去标注参数类型。
举例
1 | // 没有推断类型 |
注意
有时候显式写出类型更易读,有时候去掉它们更易读。没有什么法则说哪种更好;对于如何让代码更易读,你必须做出自己的选择。
7.4 使用局部变量
我们迄今为止所介绍的所有Lambda表达式都只用到了其主体里面的参数。但Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。
举例
1 | int x = 1; |
说明
Lambda 可以无限制的获取实例变量和静态变量,但局部变量必须显示声明为 final 类型、或者事实上是 final 类型。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。(注:捕获实例变量可以被看作捕获最终局部变量this。)
为什么局部变量有这些限制
- 实例变量保存在堆中,局部变量保存在栈中。如果 Lambda 可以直接访问局部变量,则使用 Lambda的线程,它可能会在分配该局部变量的线程收回该局部变量之后访问该局部变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
- 这一限制不鼓励你使用改变外部变量的典型命令式编程模式
7.5 方法引用
方法引用可以让你重复的使用现有的方法定义,并像 Lambda 一样传递他们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感觉也更自然。
举例
1 | //使用 Lambda 表达式 |
7.5.1 普通方法引用
1 | //定义 |
7.5.1 构造方法引用
1 | //定义 |
7.5.1 静态方法引用
1 | //定义 |