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

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

废话不多说,今天就来见识见识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舒服多了!

弄一个简单数据当然容易啊,但是来个斐波那契数列呢?那当然也没问题啦! 摆上!
- 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<Integer> 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以内的质数?动动手再往下瞅?等你哦!

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从入门到放弃!》,温故而知新啊!

下面的运用主要包括,过滤、类型转换、归并、累加、统计。当然累加中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香了!

依然是上文中组装的那个集合,我们来根据书籍名称分个组,两行代码直接解决!如果使用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)]}
要不来个秀一点的?根据名称和价格,返回页码数的分组!

- 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]}

其实这个可读性就没那么高了,估计要瞅一下,才能大概明白是什么意思。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肯定帮我们想到了!

比如想获取每种书籍的页数
- //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!

- 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?概念性的东西不多啰嗦,看一看例子就明白了!

我们都能发现流式编程书写起来简单许多,不过如果一个逻辑中流式编程特别多,也就是业务太复杂了。或者说,一些流式编程可以复用,能不能抽取出来,然后相当于一个方法直接调用了? 比如: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();
无参,直接返回一个类型,这个之前代码中就有简单实现的,来个简单的例子吧。就生成一个随机数~

- public static void main(String[] args) {
- Supplier
supplier = () -> ThreadLocalRandom.current().nextInt(1, 50); - Integer number = supplier.get();
- System.out.println("number = " + number);
- //number = 17
- }
是不是有点眼熟?
主要的接口应该就这几个,其他的都是一些功能延伸,若果这几个简单的能看懂的话,其他的也不在话下!

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