• 一起来领略JDK8中的流式编程的魅力


            五柳先生有个习惯,“好读书,不求甚解”!看来今天也要学习学习五柳先生了,JDK中流式编程魅力实在太大,简而言之,就是太方便了!那个程序员能抵挡这种考验?

            

            至于流式编程的底层原理,哦,这个我是没关心,仅仅是“不求甚解”,底层实现什么的,告辞,在下实在卷不动了!

            

            废话不多说,今天就来见识见识JDK中的流式编程,或者说是lambda编程,据说最初是根据希腊字母λ命名的。虽然Java中不使用这个符号,名称还是被保留了下来。

    使用流构建集合,输出规律数组

            一般来说,能使用流实现的,直接使用数组集合也能够实现,不过代码量会多许多,今天的话,只是单纯地聊一聊流式编程的实现,List、Map中的操作就不赘述了哈。

            咱们由俭入奢吧,先来个简单的顺序数组,

    1. ackage com.example.demomybatis.mytest;
    2. import com.alibaba.fastjson.JSON;
    3. import org.assertj.core.util.Lists;
    4. import java.math.BigDecimal;
    5. import java.util.*;
    6. import java.util.concurrent.ConcurrentHashMap;
    7. import java.util.concurrent.ThreadLocalRandom;
    8. import java.util.function.*;
    9. import java.util.stream.Collectors;
    10. import java.util.stream.IntStream;
    11. import java.util.stream.Stream;
    12. import static java.util.stream.Collectors.*;
    13. /**
    14. * desc:
    15. *
    16. * @author 笔下天地宽
    17. * @date 2022/8/15 15:28
    18. */
    19. public class StreamStudy {
    20. public static void main(String[] args) {
    21. //1.简单生成一个顺序数组
    22. List tempList = Stream.iterate(1, i -> i + 1).limit(20).collect(Collectors.toList());
    23. //或者
    24. List tempIntStream = IntStream.range(1, 20)
    25. //转换成stream
    26. .boxed()
    27. .collect(Collectors.toList());
    28. // tempList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
    29. System.out.println("tempIntStream = " + tempIntStream);
    30. }
    31. }
    32. /**
    33. * desc:
    34. *
    35. * @author 笔下天地宽
    36. * @date 2022/8/15 15:25
    37. */
    38. @Data
    39. public class Book {
    40. /**
    41. * desc :名字
    42. */
    43. private String name;
    44. /**
    45. * desc: 价格
    46. */
    47. private BigDecimal price ;
    48. /**
    49. * desc: 页码数量
    50. */
    51. private Integer number;
    52. public Book() {
    53. }
    54. public Book(String name, BigDecimal price, Integer number) {
    55. this.name = name;
    56. this.price = price;
    57. this.number = number;
    58. }
    59. }
    60. /**
    61. * desc:
    62. *
    63. * @author 笔下天地宽
    64. * @date 2022/8/16 10:39
    65. */
    66. @Data
    67. public class Pen implements Serializable {
    68. /**
    69. * desc :名字
    70. */
    71. private String name;
    72. /**
    73. * desc: 价格
    74. */
    75. private BigDecimal price ;
    76. public Pen() {
    77. }
    78. public Pen(String name, BigDecimal price) {
    79. this.name = name;
    80. this.price = price;
    81. }
    82. }

         我把所有的引用包都丢上去了,还有两个对象类,后续就直接上关键代码了哈,这个简单数据没啥可说的,要么迭代,要么取出范围内的数字,然后collect一下,返回一个集合就行了,需要的时候,肯定比写个foreach舒服多了!

            

            弄一个简单数据当然容易啊,但是来个斐波那契数列呢?那当然也没问题啦! 摆上!

    1. List fList = Stream
    2. // 生成数组(1,1)(1,2),(2,3),(3,5),(5,8)... 因为斐波那契数列需要前一位和前两位,故而临时存一下倒数第二位
    3. .iterate(new int[]{1, 1}, i -> new int[]{i[1], i[0] + i[1]}).limit(20)
    4. // 保留倒数第二位,因为倒数第二位是从斐波那契数列的第一个数字开始的
    5. .map(vo -> vo[0]).collect(Collectors.toList());
    6. System.out.println("fList = " + fList);
    7. //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

             也就是根据斐波那契数列的属性,生成一个数组,然后筛选保留,最后得到结果。只要有特定规律,一般来说都能通过lambda生成对应集合。下面咱们通过简单的随即数生成,来体会下lambda聚合的魅力所在!

            简单生成5个随即数,不定制,double类型,

    1. List doubleList = Stream.generate(Math::random).limit(5).collect(Collectors.toList());
    2. System.out.println("doubleList = " + doubleList);
    3. //doubleList = [0.051194606060667724, 0.32254338606020416, 0.7561004043101184, 0.5811685207466939, 0.1405678285694163]

             想自己定制下?行啊,一起瞅一瞅

    1. //想要定制的话,可以写个无参方法进行定制
    2. List<Integer> integerList = Stream.generate(StreamStudy::random).limit(10).collect(Collectors.toList());
    3. System.out.println("integerList = " + integerList);
    4. //integerList = [39, 42, 40, 25, 46, 41, 46, 24, 18, 17]
    5. /**
    6. * desc : 无参化定制
    7. * @return
    8. */
    9. public static Integer random(){
    10. return ThreadLocalRandom.current().nextInt(1, 50);
    11. }

            还要写个方法啊?不是说流式编程很简单么?

            嗨!这个不是一步一步来么?直接上硬菜吃着硌牙不是?方法先干掉!

    1. // 或者这样也可以
    2. List tempIntList = IntStream.generate(new IntSupplier() {
    3. @Override
    4. public int getAsInt() {
    5. return ThreadLocalRandom.current().nextInt(1, 50);
    6. }
    7. }).limit(10).boxed().collect(Collectors.toList());
    8. System.out.println("integerList = " + tempIntList);
    9. //integerList = [2, 30, 31, 8, 15, 30, 39, 37, 38, 7]
    10. // 又或者这样?
    11. List tempStreamIntList = Stream.generate(new Supplier() {
    12. @Override
    13. public Integer get() {
    14. return ThreadLocalRandom.current().nextInt(1, 50);
    15. }
    16. }).limit(10).collect(Collectors.toList());

             lambda里面直接实现一个接口,这个好点了吧?这种看着还是蛮清晰的,当然你想彻底摒弃这种匿名内部类?可以啊,来个最终的!

    1. List integerListOther = Stream.generate(() -> ThreadLocalRandom.current().nextInt(1, 50)).limit(10).collect(toList());
    2. System.out.println("tempStreamIntList = " + tempStreamIntList);

    直接实现流式Supplier的具体代码,然后进行数据收集。要不要练个手?使用流式编程生成个100以内的质数?动动手再往下瞅?等你哦!

            

    O啦,上个代码,接着看!

    1. String intString = IntStream.range(2, 100)
    2. // 质数筛选,比如31,比较16以内的数字,能被整除就代表不是质数,毕竟不是质数的话,前面一半有质因数,后面一半才有
    3. .filter(num -> IntStream.range(2, num).noneMatch(i -> 2 * i < num && num % i == 0))
    4. //转换Stream,呃,转个String,不用list了
    5. .boxed().map(String::valueOf).collect(Collectors.joining(","));
    6. System.out.println("intString = " + intString);
    7. // intString = 2,3,4,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97

     lambda 简单运用

            这个提一下吧,如果还不熟练,赶紧去看下《JDK8从入门到放弃!》,温故而知新啊!

            

            下面的运用主要包括,过滤、类型转换、归并、累加、统计。当然累加中BigDecimal与Integer、Double是有区别的,这个在计算的时候需要注意下哈。

    1. List bookList = getBookList();
    2. //lambda 简单运用
    3. Set set = bookList.stream()
    4. // 过滤,剩余的number必须大于110
    5. .filter(vo -> vo.getNumber() > 110)
    6. // 跳过第一个
    7. .skip(1)
    8. // 类型转换,返回自己需要的类型,这里直接获取价格,也可以新建另一个类型get/set,组装新的类型,最后返回新类型的Collection
    9. .map(vo -> {
    10. return vo.getName();
    11. })
    12. // 重新组合成set,也可以拼接成字符串(Collectors静态应用了,可以省略) .collect(joining(",")); 也可以.foreach 遍历处理数据秀
    13. .collect(toSet());
    14. System.out.println("set = " + set);
    15. //set = [历史, 数学, 语文, 地理]
    16. // 简单看下归并
    17. BigDecimal decimal = bookList.stream()
    18. // 类型转换,返回自己需要的类型,这里直接获取价格,也可以新建另一个类型get/set,组装新的类型,最后返回新类型的Collection
    19. .map(vo -> {
    20. return vo.getPrice();
    21. })
    22. // 也可进行累加,或者这样写.reduce(BigDecimal.ZERO,BigDecimal::add),也可以统计个数哦。使用.count()!嘿嘿just so so?;
    23. .reduce(BigDecimal::add).get();
    24. System.out.println("decimal = " + decimal);
    25. //decimal = 162.0
    1. // 集合组装
    2. public static List getBookList(){
    3. Book book = new Book("语文", new BigDecimal("22.8"), 140);
    4. Book book1 = new Book("语文", new BigDecimal("25"), 160);
    5. Book book2 = new Book("数学", new BigDecimal("21"), 110);
    6. Book book3 = new Book("数学", new BigDecimal("22.8"), 120);
    7. Book book4 = new Book("英语", new BigDecimal("30"), 105);
    8. Book book5 = new Book("历史", new BigDecimal("20.6"), 120);
    9. Book book6 = new Book("地理", new BigDecimal("19.8"), 126);
    10. return Lists.list(book,book1,book2,book3,book4,book5,book6);
    11. }
    12. }
    集合分组

     分组这个就很关键了,如果你用不到,感觉无所谓,若是真的用到了,你就会发现,lambda真是,太TMD香了!

            

            依然是上文中组装的那个集合,我们来根据书籍名称分个组,两行代码直接解决!如果使用foreach,七八行代码都不止!估计还要创建好几个List,然后让回收器帮忙回收。

    1. // a.直接过滤分组
    2. Map> nameList = bookList.stream()
    3. // 过滤大于20的
    4. .filter(book -> book.getPrice().compareTo(new BigDecimal("20")) > 0)
    5. // 也可先用 .sorted(Comparator.comparing(Book::getPrice)) 排序,sort也可以自定义,sort((a,b)>{return ...;})
    6. // 通过名称分组
    7. .collect(groupingBy(Book::getName));
    8. System.out.println("nameList = " + nameList);
    9. //nameList = {历史=[Book(name=历史, price=20.6, number=120)], 数学=[Book(name=数学, price=21, number=110), Book(name=数学, price=22.8, number=120)], 语文=[Book(name=语文, price=22.8, number=140), Book(name=语文, price=25, number=160)], 英语=[Book(name=英语, price=30, number=105)]}

             要不来个秀一点的?根据名称和价格,返回页码数的分组!

            

    1. Map> listMap = bookList.stream().collect(groupingBy(vo -> vo.getName() + vo.getPrice(), mapping(t -> t.getNumber(), toList())));
    2. System.out.println("listMap = " + listMap);
    3. // listMap = {地理19.8=[126], 语文22.8=[140], 数学22.8=[120], 语文25=[160], 数学21=[110], 英语30=[105], 历史20.6=[120]}

             

            其实这个可读性就没那么高了,估计要瞅一下,才能大概明白是什么意思。mapping(t -> t.getNumber(), toList()),就是对Map中的value进行处理组装,最终返回到流的出口。

    或者你想分组后再做个转换,如转换成Pen的名字和价格?也行!摆上。

    1. List bookList = getBookList();
    2. Map> setPenMap = bookList.stream().collect(groupingBy(Book::getName, mapping(t -> {
    3. return new Pen(t.getName() + "笔", t.getPrice());
    4. }, toSet())));
    5. System.out.println("setPenMap = " + setPenMap);
    6. // setPenMap = {历史=[Pen(name=历史笔, price=20.6)], 数学=[Pen(name=数学笔, price=21), Pen(name=数学笔, price=22.8)], 语文=[Pen(name=语文笔, price=22.8), Pen(name=语文笔, price=25)], 英语=[Pen(name=英语笔, price=30)], 地理=[Pen(name=地理笔, price=19.8)]}

    归并转Map

    许多时候,可能分组之后,我们并不想要一个list,而是想要其中的一个字段作为value,或者是几个字段作为value,怎么弄呢?lambda肯定帮我们想到了!

            

    比如想获取每种书籍的页数

    1. //Book::getName 是key赋值,或者写成vo->vo.getName()
    2. //Book::getNumber 是value赋值,也可以写成v->v.getNumber()
    3. //(a,b->a) 是一个BinaryOperator,是归并原则,就是有两个key是一样,取的是第一个的value保留,如果(a, b) -> b,这样就是保留第二个value了
    4. Map map = bookList.stream().collect(toMap(Book::getName, Book::getNumber, (a, b) -> a));
    5. System.out.println("map = " + map);
    6. //map = {历史=120, 数学=110, 语文=140, 英语=105, 地理=126}

            要不key一样,value不淘汰了?比如我想计算每种书籍的总价格,不能直接抹掉后面的哟!

        这样其实算是分组了,不过我更感觉像map处理,放到这里了,哈哈哈!摆上!分组后直接把价格进行累加,后面通过key就能直接获取总价格了。
    
    1. Map decimalMap = bookList.stream().collect(groupingBy(Book::getName, reducing(BigDecimal.ZERO, book -> book.getPrice(), BigDecimal::add)));
    2. System.out.println("decimalMap = " + decimalMap);
    3. // decimalMap = {历史=20.6, 数学=43.8, 语文=47.8, 英语=30, 地理=19.8}

            如果累加页呢,不是说BigDecimal和Integer不一样么?那更easy!

            

    1. Map integerMap = bookList.stream().collect(groupingBy(Book::getName, summingInt(vo -> vo.getNumber())));
    2. System.out.println("integerMap = " + integerMap);
    3. // integerMap = {历史=120, 数学=230, 语文=300, 英语=105, 地理=126}

             好了,我平时工作中常用的lambda操作已经全部在上面了,如果那位老铁有其他要求,可以写在评论,一起唠嗑唠嗑哈。

            下面来说几个接口吧,或者说是FunctionalInterface?概念性的东西不多啰嗦,看一看例子就明白了!

            

            我们都能发现流式编程书写起来简单许多,不过如果一个逻辑中流式编程特别多,也就是业务太复杂了。或者说,一些流式编程可以复用,能不能抽取出来,然后相当于一个方法直接调用了? 比如:t->t.getName(); 

            当然可以!

            JDK8中为我们提供了这么一个注解 @FunctionalInterface!被这个注解注释的接口,有且只能有一个接口是未被实现的,可以没有其他的接口,也可以其他接口是默认实现的,都可以。我们来看看JDK8中帮我们提供的几个关键接口。

    Predicate

             关键接口:

       boolean test(T t);
    

            这是一个判断的lambda表达式定义,比如你可以这样写:

    1. public static void main(String[] args) {
    2. Predicate predicate = vo->vo.getNumber() > 110;
    3. Book book = new Book("PE", new BigDecimal("23"), 120);
    4. boolean flag = predicate.test(book);
    5. System.out.println("flag = " + flag);
    6. //flag = true
    7. }
        vo->vo.getNumber() > 110  这个表达式肯定是不能作为入参传入方法的,一般是在方法中直接实现,但是定义了Predicate之后,这个表达式就相当于一个参数了,也就是一个判断的lambda表达式,可以直接进行传递调用。
    

            下面再看下其他的几个,直接来几行代码,大家先体会下哈。

    Function  

            关键接口:

        R apply(T t);

            T为入参,R为返回的参数,相当于类型转换。

    1. public static void main(String[] args) {
    2. Function function = vo->vo.getPrice();
    3. Book book = new Book("PE", new BigDecimal("23.5"), 120);
    4. BigDecimal price = function.apply(book);
    5. System.out.println("price = " + price);
    6. //price = 23.5
    7. }
    Consumer

            关键接口 

        void accept(T t);

            这个无返回,只是类型的处理。

    1. Consumer consumer = vo->vo.setPrice(new BigDecimal("33.8"));
    2. Book book = new Book("PE", new BigDecimal("23.5"), 120);
    3. consumer.accept(book);
    4. System.out.println("book = " + book);
    5. // book 价格已经修改
    6. // book = Book(name=PE, price=33.8, number=120)
    Supplier

              关键接口

        T get();

               无参,直接返回一个类型,这个之前代码中就有简单实现的,来个简单的例子吧。就生成一个随机数~

            

    1. public static void main(String[] args) {
    2. Supplier supplier = () -> ThreadLocalRandom.current().nextInt(1, 50);
    3. Integer number = supplier.get();
    4. System.out.println("number = " + number);
    5. //number = 17
    6. }

             是不是有点眼熟?

            主要的接口应该就这几个,其他的都是一些功能延伸,若果这几个简单的能看懂的话,其他的也不在话下!

            

            个人感觉吧,这些接口作为入参,代码的可读性估计会变差一点,你不信,来列举一个Collectors里面的一个接口实现,看看你眼力如何

            public static  Collector> groupingBy(Function classifier, Collector downstream) {
        return groupingBy(classifier, HashMap::new, downstream);}
            乍一看,我都没看明白这方法入参都是啥子! 
            我再乍!
            三乍!
            好了,大概看明白了一点,要知道咋调用?对比自己的参数一个个看吧,可能还需要一点点的,嗯,小测试!就调用这种方法,没测试,我是不敢上线啊!
            
            总的来说,流式编程简洁方便,许多功能都已经帮忙封装好了,不过就可读性而言,稍稍差了那么一丢丢,在编写代码的时候最好加上注释,不然一个方法里面来个十个八个Consumer,Function,Predicate。然后在方法中层层嵌套,最后估计都没有review的欲望了~
            好了,流式编程就扯到这里吧,有什么高见的话可以在评论区交流。
            对了,都看到这里了,顺便点个赞呗!
            ​​​​​​​
            no sacrifice,no victory!
            
    

  • 相关阅读:
    降低半导体金属线电阻的沉积和蚀刻技术
    每日一练 | 网络工程师软考真题Day41
    C++泛型编程:函数模板 和 类模板详细介绍。
    C/C++数据结构——挖沟(Kruskal算法+并查集)
    Python的高阶玩法:面向对象编程思路在SOA中的使用
    Keyword2Text: 一种即插即用的可控文本生成方法
    一文读懂工业以太网设备的发展史
    每日一题:web常见的攻击方式有哪些?如何防御?
    tensorflow2.0 mnist手写数字识别 并验证几张图片以查看效果
    Qt 日志模块的个性化使用
  • 原文地址:https://blog.csdn.net/zsah2011/article/details/126365407