java8的函数式编程

原文

Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合

Stream和parallelStream的区别

1:函数式编程

在很多其他的编程语言里面,都可以实现函数式的编程,也就是函数可以作为变量去灵活使用,但是java一直都不可以,之前很多都使用一些匿名内部类这种丑的亚批的代码。java8之后算是可以使用伪函数式编程,其实也是应用了单方法接口去实现。并且设计出了lambda语法。

1.1:lambda

在lambda之前实现thread(真的很丑这代码)。

   Thread thread = new Thread(new Runnable() {
       @Override
       public void run() {
           System.out.println("xxx");
       }
   });

在lambda实现thread

Thread thread = new Thread(()-> System.out.println("xx"));

1.2:函数式接口

其实上面的runnable接口就是一个函数式接口,标志就是用@FunctionalInterface实现的接口,我们也可以自己实现一个:

    @FunctionalInterface
    public interface FunctionInterfaceDemo {
        String run(int i);
        //如果写两个方法就会报错
    }

然后就可以当成函数变量使用了,比如说你根据不同的入参调整成不同执行策略fuction,最后去执行这个fuction,就非常的函数式了。

    FunctionInterfaceDemo functionDemo = p->{
                p++;
                return String.valueOf(p);
            };

2:大杀器-stream

如果说上面lambda只是美化了代码而已,那么lambda配合stream使用就是非常装逼的无敌大杀器了。

2.1:foreach

其实最常用的就是foreach了,直接展示一个demo,可以用lambda形式进行遍历

           //构建个User对象的list,后面都会使用到 User(id,name,age)
            List<User> userList = Lists.newArrayList();
            userList.add(new User(1, "aaa", 18));
            userList.add(new User(2, "bbb", 21));
            userList.add(new User(3, "ccc", 25));
            userList.stream().forEach(user->{
                System.out.println(user.getName());
            });

2.2:filter

filter可以按照你的要求进行过滤,非常骚包

            userList.stream().filter(user -> user.getAge() > 18).forEach(user -> {
                //输出age大于18的user的name
                System.out.println(user.getName());
            });

2.3:collect实现list转map

collect可以实现各种集合之间按照一定自定义规则的转换,最为典型的就是list转换map的例子

    Map<String, User> map = userList.stream().collect(Collectors.toMap(User::getName, pojo -> pojo, (k1, k2) -> k1));
    //【解释下toMap的3个参数】:
    //第一个参数是用哪个字段作为key
    //第二个参数是map的value类型,可以取原来的对象,也可以是原来对象的某个字段 比如pojo->pojo改为 User::getAge 就是value只收集user的age字段
    //第三个参数(k1, k2) -> k1)是可选的,在key冲突的时候丢弃其实一个,如果没有这个参数会直接报错
    //这边建议第三个参数必须强制设置一个,不然要是重复key程序直接会报错,非常麻瓜。

20220909补充:原文链接

  1. 转成一对一的,一个id对应一个对象,后面的k1,k2 是指定一种覆盖规则,防止key冲突
Map<Long, User> maps = userList.stream()
		.collect(Collectors.toMap(User::getId, Function.identity(), (k1, k2) -> k2));  
  1. 转成一对一的,一个id对应一个属性
Map<Long, String> maps = userList.stream()
		.collect(Collectors.toMap(User::getId, User::getAge, (k1, k2) -> k2));
  1. 转成一对多的,一个id对应多个对象
Map<Integer, List<Apple>> groupBy = appleList.stream()
		.collect(Collectors.groupingBy(Apple::getId));

2.4:collect+map抽取pojo对象的某个字段

    List<String> names = userList.stream().map(User::getName)
    .collect(Collectors.toList());

2.5:list实现排序

    //数字类型list排序
    List<Integer> sortList = list.stream().sorted(Comparator.comparing(p -> p))
    .collect(Collectors.toList());
     
    //对象类型list排序 
    List<Pair> sortList = list.stream()
    .sorted((p1, p2) -> p2.getValue() - p1.getValue()).collect(Collectors.toList()); 
    // 这里是逆序 ,如果是跑p1-p2就是自然序

2.6:Predicate和Consumer

在这些stream的各种操作组合骚包操作下,有时候直接写一个lambda显得有点丑,特别是这个lambda方法体内东西很多的时候,这个时候就可以使用这2个内置的函数先行定义lambda方法体,这样就非常灵活骚包,而且还可以分开写,痛击你的同事让他看不懂。

            Predicate<User> predicate = p -> p.getName().startsWith("s");
            Consumer<User> consumer = p -> {
                System.out.println(p.getName());
                System.out.println(p.getAge());
            };
            userList.stream().filter(predicate).forEach(consumer);

2.7:Stream和parallelStream的区别

更详细的讲解:Stream和parallelStream的区别

stream是单管道流,parallelStream是多管道流。建议符合以下条件时使用parallerStream:

  • 不关心执行顺序
  • 没有并行处理的并发问题
  • 处理事件涉及io阻塞操作,业务处理事件较长

下面我们来测试下stream和parallerStream的执行对比:

            List<User> userList = Lists.newArrayList();
            for (int i = 0; i < 1000; i++) {
                userList.add(new User(1, "aaa", 18));
            }
            AtomicInteger count = new AtomicInteger(0);
            long start = System.currentTimeMillis();
            userList.parallelStream().forEach(p -> {
                count.addAndGet(p.getId());
                //模拟业务处理
                try {
                    Thread.sleep(10L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            long duration = System.currentTimeMillis() - start;
            System.out.println("count = "+count.get()+" duration = " + duration);
    //执行结果如果换成stream就会慢很多

2.8:实现原理和forkjoin框架

其实stream和parallelStream都是通过fork/join框架去执行的,大概是做了这么几件事

  1. 使用自带的ForkJoinPool.commonPool()这个公共线程池去执行任务
  2. 然后这些任务会被拆分成几个小任务进行对应的流式处理
  3. 处理完成后类似future这种方式得到所有结果集再进行合并

这里有一点特别要注意,stream和parallelStream的第一个任务都是默认使用主线程的,所以异常不会逃逸,但是parallelStream的任务数量一旦大于1,就会启动forkjoin的线程,如果不处理好异常,异常就会逃逸掉了。

3:总结

总的来说lambda加stream的组合够骚够装逼,也能简化我们开发的复杂度,并且让代码更加帅!推荐使用!

其他还有很多stream的各种方法可以按照实际场景去尝试使用看看哦!

optional

在这里插入图片描述

带案例的文章

https://blog.csdn.net/mu_wind/article/details/109516995

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值