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

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

        一起来领略JDK8中的流式编程的魅力_第1张图片

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

        一起来领略JDK8中的流式编程的魅力_第2张图片

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

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

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

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

ackage com.example.demomybatis.mytest;

import com.alibaba.fastjson.JSON;
import org.assertj.core.util.Lists;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.stream.Collectors.*;

/**
 * desc:
 *
 * @author 笔下天地宽
 * @date 2022/8/15 15:28
 */
public class StreamStudy {

    public static void main(String[] args) {
        //1.简单生成一个顺序数组
        List tempList = Stream.iterate(1, i -> i + 1).limit(20).collect(Collectors.toList());
        //或者
        List tempIntStream = IntStream.range(1, 20)
                //转换成stream
                .boxed()
                .collect(Collectors.toList());
        //  tempList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
        System.out.println("tempIntStream = " + tempIntStream);
    }
}


/**
 * desc:
 *
 * @author 笔下天地宽
 * @date 2022/8/15 15:25
 */
@Data
public class Book {

    /**
     * desc :名字
     */
    private String name;
    /**
     * desc: 价格
     */
    private BigDecimal price ;
    /**
     * desc: 页码数量
     */
    private Integer number;

    public Book() {
    }

    public Book(String name, BigDecimal price, Integer number) {
        this.name = name;
        this.price = price;
        this.number = number;
    }
}

/**
 * desc:
 *
 * @author 笔下天地宽
 * @date 2022/8/16 10:39
 */
@Data
public class Pen implements Serializable {

    /**
     * desc :名字
     */
    private String name;
    /**
     * desc: 价格
     */
    private BigDecimal price ;

    public Pen() {
    }

    public Pen(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }
}


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

        一起来领略JDK8中的流式编程的魅力_第3张图片

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

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

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

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

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

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

 //想要定制的话,可以写个无参方法进行定制
        List integerList = Stream.generate(StreamStudy::random).limit(10).collect(Collectors.toList());
        System.out.println("integerList = " + integerList);
        //integerList = [39, 42, 40, 25, 46, 41, 46, 24, 18, 17] 

/**
     * desc : 无参化定制
     * @return
     */
    public static Integer random(){
        return ThreadLocalRandom.current().nextInt(1, 50);
    }

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

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

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

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

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

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

        一起来领略JDK8中的流式编程的魅力_第4张图片

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

String intString = IntStream.range(2, 100)
                // 质数筛选,比如31,比较16以内的数字,能被整除就代表不是质数,毕竟不是质数的话,前面一半有质因数,后面一半才有
                .filter(num -> IntStream.range(2, num).noneMatch(i -> 2 * i < num && num % i == 0))
                //转换Stream,呃,转个String,不用list了
                .boxed().map(String::valueOf).collect(Collectors.joining(","));
System.out.println("intString = " + intString);
        //  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从入门到放弃!》,温故而知新啊!

        一起来领略JDK8中的流式编程的魅力_第5张图片

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

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

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

        一起来领略JDK8中的流式编程的魅力_第6张图片

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

 // a.直接过滤分组
        Map> nameList = bookList.stream()
                // 过滤大于20的
                .filter(book -> book.getPrice().compareTo(new BigDecimal("20")) > 0)
                // 也可先用  .sorted(Comparator.comparing(Book::getPrice)) 排序,sort也可以自定义,sort((a,b)>{return ...;})
                // 通过名称分组
                .collect(groupingBy(Book::getName));
        System.out.println("nameList = " + nameList);
        //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)]}
     

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

        一起来领略JDK8中的流式编程的魅力_第7张图片

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

         一起来领略JDK8中的流式编程的魅力_第8张图片

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

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

List bookList = getBookList();
Map> setPenMap = bookList.stream().collect(groupingBy(Book::getName, mapping(t -> {
            return new Pen(t.getName() + "笔", t.getPrice());
        }, toSet())));
System.out.println("setPenMap = " + setPenMap);
// 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肯定帮我们想到了!

        一起来领略JDK8中的流式编程的魅力_第9张图片

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

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

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

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

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

        一起来领略JDK8中的流式编程的魅力_第10张图片

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

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

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

        一起来领略JDK8中的流式编程的魅力_第11张图片

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

        当然可以!

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

Predicate

         关键接口:

   boolean test(T t);

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

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

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

Function  

        关键接口:

    R apply(T t);

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

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

        关键接口 

    void accept(T t);

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

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

          关键接口

    T get();

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

        一起来领略JDK8中的流式编程的魅力_第12张图片

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

         是不是有点眼熟?

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

        一起来领略JDK8中的流式编程的魅力_第13张图片

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

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

你可能感兴趣的:(java学习梳理,java-基础知识,lambda,stream,bigDecimal,Map转换,分组)