我始终认为通过案例学习是最好的掌握一个技能的方式,因此我们还是通过之前文章中的案例,对Stream编程中涉及到的具体的方法进行详细的学习。
首先初始化一个List集合,用于后续讲解所用。
List<Sku> list; @Before public void init() { list = CartService.getCartSkuList(); }
我们仍旧使用之前的购物车数据
首先讲解中间操作,它分为有状态和无状态两种形式
@Test public void filterTest() { list.stream() // 判断是否符合一个断言(商品类型是否为书籍),不符合则过滤元素 .filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory())) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这个操作的目的是:从商品列表中选择所有的书籍类型,其余类型过滤
运行结果:
{"skuCategory":"BOOKS","skuId":121,"skuName":"Java编程思想","skuPrice":100.0,"totalNum":10,"totalPrice":100.0} {"skuCategory":"BOOKS","skuId":3,"skuName":"程序化广告","skuPrice":80.0,"totalNum":10,"totalPrice":80.0}
总结:filter用于判断集合数据是否符合一个断言,不符合则过滤掉元素
filter结果为TRUE,表示通过测试,数据不过滤;结果为FALSE,表示未通过测试,数据被过滤
@Test public void mapTest() { list.stream() // 将一个元素转换为另一个数据类型的元素 .map(sku -> sku.getSkuName()) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这个操作的目的是:从商品列表中获取每个商品的名称,并打印
运行结果:
"无人机" "VR一体机" "牛仔裤" "衬衫" "Java编程思想" "程序化广告"
总结:map用于将一个元素转换为另一个(类型的)元素
@Test public void flatMap() { list.stream() // 这里将商品名称切分为一个字符流,最终输出 .flatMap(sku -> Arrays.stream(sku.getSkuName().split(""))) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这个操作的目的是:将商品名称逐字进行拆分
运行结果:
"无" "人" "机" "V" "R" "一" "体" "机" "牛" "仔" "裤" "衬" "衫" "J" "a" "v" "a" "编" "程" "思" "想" "程" "序" "化" "广" "告"
从运行结果可以直观看出:flatMap接收一个流 并返回一个新的流 ,且这个新的流能够与其他的流进行合并
我们看下flatMap方法的声明
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
我们可以清楚的看到,flatMap接受一个流,并返回一个新流
总结一下,flatMap 是获取了对象中集合类属性,组成一个新的流供我们使用。可以用在Map类数据的去重场景。
比如:key=商户 value=商户分类,我们要统计所有商户的类别,就可以将商户分类扁平化为一个流,进行distinct操作。
@Test public void peekTest() { list.stream() // 对一个流中的所有元素进行处理,但与forEach不同之处在于,peek是一个中间操作, // 操作完成还能被后续使用。forEach是一个终端操作,处理完之后流就不能被操作了。 .peek(sku -> System.out.println(sku.getSkuName())) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这个操作的目的为:迭代商品列表,逐个输出商品名,并最终通过forEach迭代一次。
运行结果:
无人机 {"skuCategory":"ELECTRONICS","skuId":2,"skuName":"无人机","skuPrice":1000.0,"totalNum":10,"totalPrice":1000.0} VR一体机 {"skuCategory":"ELECTRONICS","skuId":1,"skuName":"VR一体机","skuPrice":2100.0,"totalNum":10,"totalPrice":2100.0} 牛仔裤 {"skuCategory":"CLOTHING","skuId":4,"skuName":"牛仔裤","skuPrice":60.0,"totalNum":10,"totalPrice":60.0} 衬衫 {"skuCategory":"CLOTHING","skuId":13,"skuName":"衬衫","skuPrice":120.0,"totalNum":10,"totalPrice":120.0} Java编程思想 {"skuCategory":"BOOKS","skuId":121,"skuName":"Java编程思想","skuPrice":100.0,"totalNum":10,"totalPrice":100.0} 程序化广告 {"skuCategory":"BOOKS","skuId":3,"skuName":"程序化广告","skuPrice":80.0,"totalNum":10,"totalPrice":80.0}
看输出效果,peek与forEach是交替执行的,并不是先执行完peek之后才执行forEach
可以看到,流是惰性执行的 先执行中间操作,再执行终端操作,交替打印(peek是无状态的)
@Test public void sortTest() { list.stream() .peek(sku -> System.out.println(sku.getSkuName())) // 根据Sku的价格进行升序排列 .sorted(Comparator.comparing(Sku::getTotalPrice)) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
运行结果:
无人机 VR一体机 牛仔裤 衬衫 Java编程思想 程序化广告 {"skuCategory":"CLOTHING","skuId":4,"skuName":"牛仔裤","skuPrice":60.0,"totalNum":10,"totalPrice":60.0} {"skuCategory":"BOOKS","skuId":3,"skuName":"程序化广告","skuPrice":80.0,"totalNum":10,"totalPrice":80.0} {"skuCategory":"BOOKS","skuId":121,"skuName":"Java编程思想","skuPrice":100.0,"totalNum":10,"totalPrice":100.0} {"skuCategory":"CLOTHING","skuId":13,"skuName":"衬衫","skuPrice":120.0,"totalNum":10,"totalPrice":120.0} {"skuCategory":"ELECTRONICS","skuId":2,"skuName":"无人机","skuPrice":1000.0,"totalNum":10,"totalPrice":1000.0} {"skuCategory":"ELECTRONICS","skuId":1,"skuName":"VR一体机","skuPrice":2100.0,"totalNum":10,"totalPrice":2100.0}
和上述案例对比,这个案例在于增加了sorted排序操作。
执行完成,可以看到,peek与forEach并不是交替打印的,这是因为增加了sorted操作(sorted是有状态的)之后,必须要sort完成,才进行后续操作
public void sortTrade() { List<Sku> collect = list.stream() .sorted(// 逆序 Comparator // 排序条件1:总价逆序 .comparing(Sku::getTotalPrice, Comparator.reverseOrder()) // 排序条件2:skuId升序(自然排序) .thenComparing(Sku::getSkuId) // 排序条件3:商品单价逆序 .thenComparing(Sku::getSkuPrice, Comparator.reverseOrder()) // 自定义排序条件:根据类别 .thenComparing( // 排序字段值 Sku::getSkuCategory, // 排序规则 (type1, type2) -> { if (SkuCategoryEnum.BOOKS.equals(type1)) { // 标识type1在先,type2在后 return -1; } if (SkuCategoryEnum.CLOTHING.equals(type2)) { // 标识type1在后,type2在先 return 1; } // 两者相等 return 0; })).collect(Collectors.toList()); ; }
我们分析一下这段代码,可以看到它是根据一定的排序规则,链式地进行编码。如果掌握了Stream编程,那么这段代码写起来会很顺手,读起来也赏心悦目。
但是另一方面,这点代码由于采用了链式编程方式,每个操作都依赖之前操作的结果,对于不了解Stream编程的同学讲就比较痛苦了。
我的建议是,stream编程我们要学,对于复杂的流程如果要采用stream实现,那么请把注释写清楚,方便后续维护。
毕竟Stream被人诟病较多的地方就是:复杂逻辑下不好理解,不易测试。
我个人的建议是,注释在精不在多,关键逻辑,复杂逻辑要有注释,对自己对别人都有好处。
@Test public void distinctTest() { list.stream() .map(sku -> sku.getSkuCategory()) .distinct() // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
运行结果:
"ELECTRONICS" "CLOTHING" "BOOKS"
distinct()操作的目的为对流元素进行去重操作。原集合共6个元素,通过distinct()操作将重复的类别进行去除,最终保留不重复的类别结果。
@Test public void skipTest() { list.stream() .sorted(Comparator.comparing(Sku::getTotalPrice)) // 对价格排序之后跳过前三条 .skip(3) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这段代码的意图为:对商品的价格进行排序,跳过前三个(从小到大排序,跳过最小的三个)
运行结果:
{"skuCategory":"CLOTHING","skuId":13,"skuName":"衬衫","skuPrice":120.0,"totalNum":10,"totalPrice":120.0} {"skuCategory":"ELECTRONICS","skuId":2,"skuName":"无人机","skuPrice":1000.0,"totalNum":10,"totalPrice":1000.0} {"skuCategory":"ELECTRONICS","skuId":1,"skuName":"VR一体机","skuPrice":2100.0,"totalNum":10,"totalPrice":2100.0}
@Test public void limitTests() { list.stream() .sorted(Comparator.comparing(Sku::getTotalPrice)) // 对价格排序之后取前三条 .limit(3) // 打印终端操作结果 .forEach(item -> System.out.println(JSON.toJSONString(item, true))); }
这段代码的意图与skip刚好相反:对商品价格进行排序,取前三个(从小到大,取top3)
运行结果:
{"skuCategory":"CLOTHING","skuId":4,"skuName":"牛仔裤","skuPrice":60.0,"totalNum":10,"totalPrice":60.0} {"skuCategory":"BOOKS","skuId":3,"skuName":"程序化广告","skuPrice":80.0,"totalNum":10,"totalPrice":80.0} {"skuCategory":"BOOKS","skuId":121,"skuName":"Java编程思想","skuPrice":100.0,"totalNum":10,"totalPrice":100.0}
结果和上面的运行结果刚好互为补集。由此我们可以猜测是否可以通过skip + limit实现内存分页?
答案是肯定的,具体的方法为
通过skip 和 limit 实现一个假分页如:3条一页
第一页 skip(0 * 3).limit(3) 第二页 skip(1 * 3).limit(3) ... 第N页 skip((n - 1) * 3).limit(3)
总结为公式就是:
skip((pageNum - 1) * pageSize).limit(pageSize)
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。