Lambda表达式&Stream流-函数式编程入门教程

目录

函数式编程思想

Lambda 表达式

Stream流

常用的Stream中的API

创建Stream

第一种是直接使用.Stream()的方式来创建

第二种采用.of()方式创建

具体来看看每一个API是怎么用的

concat

max

min

findFirst

findAny

count

peek

 forEach

forEachOrdered

skip

distinct

map

flatMap

 collection

toArray

reduce


函数式编程思想

        在数学中函数就是有输入量 , 输出量的一套计算方案, 也就是拿什么东西做什么事情. 相对而言, 面向对象过分强调"必须通过对象的形式来做事情", 而函数式 思想则尽量忽略面向对象的复杂语法, 更加强调做什么,而不是以什么形式做

面向对象的思想 :

        ​做一件事情, 找一个能解决这个事情的对象, 调用对象的方法, 完成事情.

函数式编程思想 :

        ​ 只要能获取到结果, 谁去做的, 怎么做的都不重要. 重视的是结果, 不重视过程

Lambda 表达式

        Lambda 表达式是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象,是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包。

        最常见的一个例子就是新建线程,有时候为了省事,会用下面的方法创建并启动一个线程,这是匿名内部类的写法,new Thread需要一个 implements 自Runnable类型的对象实例作为参数,比较好的方式是创建一个新类,这个类 implements Runnable,然后 new 出这个新类的实例作为参数传给 Thread。而匿名内部类不用找对象接收,直接当做参数。

 new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程中run的方法实现了");
            }
        }).start();

但是这样写的话是不是觉得很土,不简洁,所以这个时候Lambda表达式就派上了用场

 new Thread(() -> {
            System.out.println("新线程中run的方法实现了");
        }).start();

小技巧: 可以使用alt+enter快速将匿名内部类和lambda之间相互转换

Lambda 表达式简化了匿名内部类的形式,可以达到同样的效果,但是 Lambda 要优雅的多。虽然最终达到的目的是一样的,但其实内部的实现原理却不相同。匿名内部类在编译之后会创建一个新的匿名内部类出来,而 Lambda 是调用 JVM invokedynamic指令实现的,并不会产生新类。

Stream流

        Stream流不只是Lambda表达式厉害,它其中还包含了很多 Java 8 中集合数据处理的利器,很多本来复杂、需要写很多代码的方法,比如过滤、分组等操作,往往使用 Stream 就可以在一行代码搞定,当然也因为 Stream 都是链式操作,一行代码可能会调用好几个方法。

        Collection接口提供Stream()方法,让我们可以对集合使用Stream API来进行各种操作。要注意的是对同一个集合进行stream操作,执行的任何操作不会对源集合产生任何影响,所以对于同一个集合你可以采用多个stream来进行操作。

        我们先来看看Stream流的接口定义,继承至BaseStream,所有的接口声明都是接收方法引用类型的参数,比如 filter方法,接收了一个 Predicate类型的参数,它就是一个函数式接口,常用来作为条件比较、筛选、过滤用,JPA中也使用了这个函数式接口用来做查询条件拼接。

常用的Stream中的API

常用的API还是有一点多,可以稍微看看下面这张图

 

创建Stream

创建Stream的方式有很多,这里介绍两种常见的:

第一种是直接使用.Stream()的方式来创建

第二种采用.of()方式创建

可能你会疑惑为什么这么写,那最好的方式就是来看看源代码:

 参数和返回值都是接受的一个泛型,所以无论你传入的什么都是可以的,需要注意的是, stream流创建后只能使用一次,创建了一个stream流使用两次会报  IllegalStateException异常。  

具体来看看每一个API是怎么用的
concat

连接两个 Stream ,不改变其中任何一个 Steam 对象,返回一个新的 Stream 对象。

private static void concatStream(){ 
    Stream<String> a = Stream.of("y","yy","yyy"); 
    Stream<String> b = Stream.of("d","e"); 
    Stream<String> c = Stream.concat(a,b); 
}
max

一般用于求数字集合中的最大值,或者按实体中数字类型的属性比较,拥有最大值的那个实体。它接收一个 Comparator<T>,上面也举到这个例子了,它是一个函数式接口类型,专门用作定义两个对象之间的比较,例如下面这个方法使用了 Integer::compareTo这个方法引用。

public static void maxi(){
        Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
        Integer reMax=stream.max(Integer::compareTo).get();
        System.out.println(reMax);
    }

 

当然,我们也可以自己定制一个 Comparator,顺便复习一下 Lambda 表达式形式的方法引用。

 public static void maxi(){
        Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
        Comparator<Integer> comparator=(x,y)->(x.intValue()<y.intValue()?-1:(x.intValue()==y.intValue()?0:1));
        Integer reMax=stream.max(comparator).get();
        System.out.println(reMax);
    }
min

与 max 用法一样,只不过是求最小值。

findFirst

获取 Stream 中的第一个元素。

findAny

获取 Stream 中的某个元素,如果是串行情况下,一般都会返回第一个元素,并行情况下就不一定了。

count

返回元素个数。

peek

建立一个通道,在这个通道中对 Stream 的每个元素执行对应的操作,对应 Consumer<T>的函数式接口,这是一个消费者函数式接口,顾名思义,它是用来消费 Stream 元素的,比如下面这个方法,把每个元素转换成对应的大写字母并输出。

 public static void testPeek(){
        Stream<String> stream=Stream.of("a","b","c");
        List<String> list=stream.peek(c-> System.out.println(c.toUpperCase())).collect(Collectors.toList());
//        list.stream().forEach(System.out::println);
    }
 forEach

和 peek 方法类似,都接收一个消费者函数式接口,可以对每个元素进行对应的操作,但是和 peek 不同的是,forEach 执行之后,这个 Stream 就真的被消费掉了,之后这个 Stream 流就没有了,不可以再对它进行后续操作了,而 peek操作完之后,还是一个可操作的 Stream 对象。

正好借着这个说一下,我们在使用 Stream API 的时候,都是一串链式操作,这是因为很多方法,比如接下来要说到的 filter方法等,返回值还是这个 Stream 类型的,也就是被当前方法处理过的 Stream 对象,所以 Stream API 仍然可以使用。

forEachOrdered

功能与 forEach是一样的,不同的是,forEachOrdered是有顺序保证的,也就是对 Stream 中元素按插入时的顺序进行消费。为什么这么说呢,当开启并行的时候,forEachforEachOrdered的效果就不一样了。

skip

跳过前 n 条数据,例如下面代码,返回结果是 c。

distinct

元素去重,例如下面方法返回元素是 a、b、c,将重复的 b 只保留了一个。

map

map方法的接口方法声明如下,接受一个 Function函数式接口,把它翻译成映射最合适了,通过原始数据元素,映射出新的类型。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

 Function的声明也是这样的,接收一个T型的参数,返回一个R型的参数,这样就可以实现元素类型的转换,用于将当前类型转换为另一个合适的类型

flatMap

当你的 Stream 是以下这几种结构的时候,需要用到 flatMap方法,用于将原有二维结构扁平化。

  1. Stream<String[]>
  2. Stream<Set<String>>
  3. Stream<List<String>>

以上这三类结构,通过 flatMap方法,可以将结果转化为 Stream<String>这种形式,方便之后的其他操作。

比如下面这个方法,将List<List<User>>扁平处理,然后再使用 map或其他方法进行操作。

 public static void testFlatMap(){
        List<String> list=Arrays.asList("a1","a2","b1","b2","c2");
        List<String> list1=Arrays.asList("a","b","c");
        List<List<String>> list2=new ArrayList<>();
        list2.add(list);
        list2.add(list1);
        Stream<List<String>> stream=list2.stream();
        List<String> stream1=stream.flatMap(Collection::stream).collect(Collectors.toList());
        stream1.forEach(System.out::println);
    }
 collection

  Collectors为我们提供了很多拿来即用的收集器。比如我们经常用到Collectors.toList()Collectors.toSet()Collectors.toMap()。另外还有比如Collectors.groupingBy()用来分组,比如下面这个例子,按照 userId 字段分组,返回以 userId 为key,List 为value 的 Map,或者返回每个 key 的个数。

// 返回 userId:List<User>
Map<String,List<User>> map = user.stream().collect(Collectors.groupingBy(User::getUserId));

// 返回 userId:每组个数
Map<String,Long> map = user.stream().collect(Collectors.groupingBy(User::getUserId,Collectors.counting()));
toArray

collection是返回列表、map 等,toArray是返回数组,有两个重载,一个空参数,返回的是 Object[]

另一个接收一个 IntFunction<R>类型参数。

reduce

它的作用是每次计算的时候都用到上一次的计算结果,比如求和操作,前两个数的和加上第三个数的和,再加上第四个数,一直加到最后一个数位置,最后返回结果,就是 reduce的工作过程。

 public static void testReduce(){
        Stream<Integer> stream=Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 9);
        Integer re=stream.reduce(1,(x,y)->x*y);
        System.out.println(re);
    }

当然还有几个map有关的函数也是比较重要的,这里就不做过多的介绍了~

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

银氨溶液

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值