lambda表达式和stream流

lambda表达式

基本格式

(Object o1, Object o2) -> {方法体}

o1、o2:方法参数

->:特定的箭头符号

{...}:方法体内容,实际的代码

注:当方法体只有一行时,花括号可省略。

函数式接口

函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

@FunctionalInterface

java提供上述注解标记一个接口为函数式接口,该注解为非强制添加注解,仅仅作为一个定义的条件约束,也就是说如果一个接口加上了该注解,那么该接口必须满足函数式接口的定义。

注:重写/定义父类的方法,不影响上述的判断规则,接口只要符合上述规则即可。

 

核心函数式接口

消费接口

Consumer<T>,接口源码如下:

 

顾名思义,该接口的accept方法实现要求传入一个泛型参数,在方法体中做出某些操作,无返回值(void),就像现实生活中的某些消费操作一样,把钱交给对方后,对方提供某些服务。该接口大多数用于集合遍历操作中(可以通过项目代码看出),对元素进行消费操作。

常见变种接口:

生产接口

Supplier<T>,接口源码如下:

 

该接口get方法实现无入参,返回值为指定泛型类型,该接口可以用来生产元素。

常见变种接口:

谓词接口

Predicate<T>,接口部分源码如下:

 

该接口test方法实现要求传入一个泛型参数t,返回值为boolean,因此该接口常常用来做元素的过滤和筛选。

常见变种接口:

函数接口

Function<T, R>,接口部分源码如下:

 

该接口apply方法实现要求传入一个泛型参数t,返回值为另一个泛型类型R,该接口主要用来进行元素的映射和转换。

常见变种接口:

类型检查

Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。类型检查描述示例如下图所示(部分图例摘自《Java8实战》)。

 

类型推断

Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名(方法名+参数类型),因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。类型推断示例如下图所示。

 

方法引用

方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。方法引用的一些例子如下图所示。

 

方法引用构建方法主要有以下三类。

静态方法的调用

当调用类的静态方法时,可以简写为类名::方法名(不带括号)的格式。

额外补充说明如下:

无参静态方法调用

可以直接使用方法引用。

具体示例代码如下:

Test类静态方法:

 

Test类main方法代码:

 

带参的静态方法引用

调用的静态方法中的参数个数,必须要和lambda参数个数保持一致,且不能包含其他外部参数,否则无法使用方法引用。

具体示例代码如下:

Test类中带参的静态方法如下:

 

Test2类中带参的静态方法如下:

 

Test类main方法代码:

 

Test类静态方法,3个参数:

 

Test类main方法代码:

 

lambda表达式中参数对象的实例方法调用

当调用lambda表达式中参数对象的实例方法时,可以简写为类名::调用方法名(不带括号)的格式。

额外补充说明如下:

无参实例方法调用

lambda参数仅有一个时,可以将其转换为方法引用;

若lambda参数多个,则无法转换为方法引用。

具体示例代码如下:

Test类,无参实例方法如下:

 

TestInterface接口如下,test方法仅有一个参数:

 

Test类main方法代码:

 

TestInterface1接口如下,test方法包含多个参数:

 

Test类main方法代码:

 

带参实例方法调用

lambda参数仅有一个时,无论实例方法参数类型和个数,均不能使用方法引用;

lambda参数为多个时,必须满足方法体中调用方法的对象为lambda参数的第一个参数,并且剩余的参数,类型和个数要和调用的方法的类型和个数匹配,且剩余参数均要在调用方法的其他参数列表里,个数只能出现一次,不允许出现其他外部参数。

具体示例代码如下:

Test类中,带参实例方法如下:

 

Test类main方法代码:

 

Test类中,带参实例方法如下:

 

Test类main方法代码:

 

 

将TestInterface接口的test方法修改为如下格式,包含3个参数:

 

Test类main方法代码:

 

外部实例对象的实例方法调用

当调用其他实例对象的实例方法时,可以简写为实例对象名::调用方法名(不带括号)的格式。

额外补充说明如下:

无参实例方法调用

不存在lambda参数时,可以使用方法引用;

存在lambda参数时,不可以使用方法引用。

具体示例代码如下:

TestInterface3接口如下:

 

TestInterface4接口如下:

 

Test类,main方法代码:

 

 

带参实例方法调用

不存在lambda参数时,无法使用方法引用;

存在lambda参数,且lambda参数和调用的实例方法参数类型、个数、顺序保持一致,则可以使用方法引用。

具体示例代码如下:

TestInterface接口代码如下:

 

TestInterface3接口代码如下:

 

Test类,实例方法如下:

 

 

Test类,main方法代码:

 

 

Stream流

基本概念

流是Java8新增的特性,简单来说,流能让你更加简单地对数据的集合进行排序、过滤、映射等处理。使用流有以下几点好处:声明性(代码更简洁、易读)、可复合(更灵活)、可并行(充分发挥性能)。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等;元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。

可以将stream流操作想象成工厂里的流水线操作,原料经过一系列的流水线加工,最终包装成产品。

 

中间/终端操作

java.util.stream.Stream中的Stream接口定义了许多操作,它们可以分为两大类,以下列代码举例说明:

List<String> names = menu.stream()

            .filter(d -> d.getCalories() > 300)(过滤)

            .map(Dish::getName)(映射)

            .limit(3)(取前3条数据)

            .collect(toList());(聚合)

其中,filter、map和limit可以连成一条流水线;collect触发流水线执行并关闭它。

可以连接起来的流操作称为中间操作。诸如 filter 或 sorted 等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理(延迟操作)。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

关闭流的操作称为终端操作。终端操作会从流的流水线生成结果,该结果是任何不是流的值,比如List、Integer甚至是void(如进行遍历操作)。

使用流的步骤

第一步:从一个数据源(如集合)调用指定方法,获取流对象;

第二步:对于流对象,可以有一个或多个中间操作链,形成一条流的流水线操作;

第三步:执行某个终端操作,流水线此时才开始执行,并生成结果。

流的创建

由值创建流:Stream.of()静态方法,传入对象/对象数组;

由数组创建流:Arrays.stream()静态方法,传入数组;

由文件创建流:Files.lines()静态方法,传入文件路径(Path);

由函数创建流:Stream. iterate()静态方法(迭代),Stream.generate()静态方法(生成)。

注:使用函数创建流时,一定要调用limit方法限制流元素的个数,否则流会无限生成元素,导致程序运行异常。

示例代码:

 

收集器

Collector<T, A, R>

接口部分源码如下

 

泛型说明:

T:进行归约操作的元素类型;

A:归约操作的中间类型;

R:归约操作最终返回的类型。

 

方法说明:

supplier():流元素容器提供;

accumulator():流元素归约操作;

combiner():将两个部分结果合并到一个结果中。该方法适用于parallelStream()并行流;

finisher():将中间类型结果转换为最终类型;

characteristics():收集器的特性。

 

posted @ 2020-09-30 10:24  yjry-th  阅读(194)  评论(0编辑  收藏  举报