zoukankan      html  css  js  c++  java
  • 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():收集器的特性。

  • 相关阅读:
    负载均衡器部署方式和工作原理
    Android 有关于* daemon not running.starting it now on port 5037 *ADB
    微信开发常用文档及参考资料
    XML解析之sax解析案例(二)使用sax解析把 xml文档封装成对象
    XML解析之sax解析案例(一)读取contact.xml文件,完整输出文档内容
    XML解析之SAX解析过程代码详解
    通过PHP current()函数获取未知字符键名数组第一个元素的值
    PHP检测链接是否是SSL连接 ,也就是判断HTTPS
    PHP反射ReflectionClass、ReflectionMethod 入门教程
    PHP 反射API说明
  • 原文地址:https://www.cnblogs.com/yjry-th/p/13753225.html
Copyright © 2011-2022 走看看