Java 8 Lambda 表达式详解
1、基础语法
Lambda 表达式基础语法:
(parameters) -> expression
或 (parameters) ->{ statements; }
先理解:这段代码可理解为一个方法,小括号里的内容是方法入参,大括号里的内容是方法体。 而这行代码,就是一个 Lambda 表达式。所以 Lambda 表达式实际是一个方法(即函数)。
Java 8 中规定:Lambda 允许把函数(即Lambda 表达式)作为一个方法的参数(类似 JS 中的闭包)。 所以:Lambda 表达式是一个对象,而这个对象实际是一个方法。所以,Lambda 表达式是一个方法对象。
但是到底是什么对象呢?这个后续再细说。
既然是一个方法,那么对自然有一些简写形式,如下:
-
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。 即()里面无需声明参数类型。()里面甚至可以什么都没有
-
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。 即()可以省略。
-
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。 即{}可以省略,同时语句后面的分号也可以省略。
-
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
就是说,小括号中的入参可省略,小括号可省略,大括号可省略,大括号中的内容还可省略。 注:小括号和小括号中的入参不能同时省略。怎么省略可详细参考:Java 8 Lambda 表达式 | 菜鸟教程
3、其他语法注意:
-
lambda 表达式只能引用标记了 final 的外层局部变量。即不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。 注:修改final肯定会报错。另外,基本类型final就是final,引用类型final只表示变量地址(即引用)不可变,变量指向的内容还是实际可变的。
-
可以在 lambda 表达式中直接访问外层的局部变量: 注:final肯定可以被直接引用了。
-
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义) 注:这点应该由编译来保证。即在lambda 表达式内部使用的局部变量肯定为final,即使不显式声明。
-
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。 注:这点肯定是的。同名肯定会出错。
二、方法引用和Lambda的关系
2.1 什么是方法引用?
-
方法引用是 lambda 表达式的一种特殊形式,即方法引用就是 Lambda 表达式(方法引用是 lambda 表达式的一种语法糖)。
-
方法引用用于获取一个已有方法的 Lambda 表达式形式。
上面两句应该说明了什么是方法引用。
2.2 方法引用的使用语法和限制
注:大写开头表示类名,小写开头表示对象。
-
引用静态方法 ContainingClass::staticMethodName 例子: String::valueOf,对应的 Lambda:(s) -> String.valueOf (s) 比较容易理解,和静态方法调用相比,只是把.换为::
-
引用特定对象的实例方法 containingObject::instanceMethodName 例子: x::toString,对应的 Lambda:() -> this.toString () 与引用静态方法相比,都换为实例的而已
-
引用特定类型的任意对象的实例方法(不建议使用,比较麻烦) ContainingType::methodName 例子: String::toString,对应的 Lambda:(s) -> s.toString () 实例方法要通过对象来调用,方法引用对应 Lambda,Lambda 的第一个参数会成为调用实例方法的对象。
-
引用构造函数 ClassName::new 例子: String::new,对应的 Lambda:() -> new String () 构造函数本质上是静态方法,只是方法名字比较特殊。
注意:第一和第三的区别在于::
后面的方法是静态还是非静态,其他一致。
参考:Java 8 之方法引用 (Method References) - 永无止境,上下求索 - CSDN 博客
三、功能接口
3.1 功能接口定义
上面,一直说Lambda 表达式是一个对象,那是什么类型的对象呢? 这个类型就是功能接口类型。
在 Java 中,功能接口(Functional interface)指只有一个抽象方法的接口。 即如果一个接口只有一个抽象方法,Java 会自动认为该接口是功能接口。同时,该接口可以作为 Lambda 表达式的对象类型。 所以:Lambda 表达式是功能接口类型的对象,但这个功能接口类型不只一个,而是一类,这点要注意。
这里分析一下:Lambda 表达式既是方法(实现),又是对象。是什么方法(实现)?功能接口的方法(实现)。是什么类型接口的对象?功能接口类型的对象。 到这里,一切就清晰了。
那如何确定一个接口是不是功能接口呢?
-
可以用
@FunctionalInterface
注解标识。如果被标识的接口不符合功能接口规则,那么是会编译报错的。
3.2 JDK 中已有的功能接口
首先:这 4 个接口都是功能接口,所以其只有一个抽象方法。而这 4 个接口的不同在于抽象方法的参数和返回结果不同。理解了这点,就看也理解这四个接口了。
-
Function.java
主要方法:R apply(T t);
功能说明: 将 Function 对象应用到输入的参数上,然后返回计算结果。 -
Consumer.java
主要方法:void accept(T t);
功能说明: 该接口表示接受单个输入参数并且没有返回值的操作。 -
Predicate.java
主要方法:boolean test(T t);
功能说明: 表示判断输入的对象是否符合某个条件。 -
Supplier.java
主要方法:T get();
功能说明: 无参数,返回一个结果。
总结一下上面的4个接口的区别:
接口 | 参数 | 返回结果 |
---|---|---|
Function | 有 | 有 |
Consumer | 有 | 无 |
Predicate | 有 | 布尔类型 |
Supplier | 无 | 有 |
参考:JDK8 函数式接口 Function、Consumer、Predicate、Supplier - 酒肉猿 - CSDN 博客
四、使用实例:
参考:例子可参考:Java 8 Lambda 表达式详解 - 程序员干货 - SegmentFault 思否 到这里,你应该就知道可以怎么用了。
五、Stream API 与 Lambda 表达式
Stream API是JDK8的一个新特性,和 Lambda 的关系在于其可以接受 Lambda 表达式参数,使代码更加简洁方便。 具体使用可参考 JDK 文档:Java 8 中文版 - 在线 API 中文手册 - 码工具 中的Interface Stream<T>
这里面有常用的map,sort等方法的接口声明。
至于 Stream 的更详细的知识,后续再写。暂时只写Lambda。
常用功能和方法说明:
六、参考:
Lambda 表达式有何用处? - FACEBOOK THEM - CSDN 博客 注:特别优秀的一篇教程,很棒。