Java 8 Lambda 表达式详解

一、Lambda 表达式

1、基础语法

Lambda 表达式基础语法:

(parameters) -> expression
或 (parameters) ->{ statements; }

先理解:这段代码可理解为一个方法,小括号里的内容是方法入参,大括号里的内容是方法体。 而这行代码,就是一个 Lambda 表达式。所以 Lambda 表达式实际是一个方法(即函数)。

Java 8 中规定:Lambda 允许把函数(即Lambda 表达式)作为一个方法的参数(类似 JS 中的闭包)。 所以:Lambda 表达式是一个对象,而这个对象实际是一个方法。所以,Lambda 表达式是一个方法对象。

但是到底是什么对象呢?这个后续再细说。

2、语法简写形式

既然是一个方法,那么对自然有一些简写形式,如下:

  1. 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。 即()里面无需声明参数类型。()里面甚至可以什么都没有

  2. 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。 即()可以省略。

  3. 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。 即{}可以省略,同时语句后面的分号也可以省略。

  4. 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

就是说,小括号中的入参可省略,小括号可省略,大括号可省略,大括号中的内容还可省略。 注:小括号和小括号中的入参不能同时省略。怎么省略可详细参考:Java 8 Lambda 表达式 | 菜鸟教程

3、其他语法注意:

  1. lambda 表达式只能引用标记了 final 的外层局部变量。即不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。 注:修改final肯定会报错。另外,基本类型final就是final,引用类型final只表示变量地址(即引用)不可变,变量指向的内容还是实际可变的。

  2. 可以在 lambda 表达式中直接访问外层的局部变量: 注:final肯定可以被直接引用了。

  3. lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义) 注:这点应该由编译来保证。即在lambda 表达式内部使用的局部变量肯定为final,即使不显式声明。

  4. 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。 注:这点肯定是的。同名肯定会出错。

二、方法引用和Lambda的关系

2.1 什么是方法引用?

  1. 方法引用是 lambda 表达式的一种特殊形式,即方法引用就是 Lambda 表达式(方法引用是 lambda 表达式的一种语法糖)。

  2. 方法引用用于获取一个已有方法的 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 个接口的不同在于抽象方法的参数和返回结果不同。理解了这点,就看也理解这四个接口了。

  1. Function.java 主要方法:R apply(T t); 功能说明: 将 Function 对象应用到输入的参数上,然后返回计算结果。

  2. Consumer.java 主要方法:void accept(T t); 功能说明: 该接口表示接受单个输入参数并且没有返回值的操作。

  3. Predicate.java 主要方法:boolean test(T t); 功能说明: 表示判断输入的对象是否符合某个条件。

  4. 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。

常用功能和方法说明:

 

六、参考:

  1. Lambda 表达式有何用处? - FACEBOOK THEM - CSDN 博客 注:特别优秀的一篇教程,很棒。

 

 

posted @ 2018-10-31 10:09  不无聊  阅读(2298)  评论(0编辑  收藏  举报