流的使用

实战演示,还是以之前我们所写的购物车案例,演示流的各种操作,前置的准备工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.stream.shopping;

import com.stream.shopping.cart.Sku;
import com.stream.shopping.service.CartService;
import org.junit.jupiter.api.BeforeEach;

import java.util.List;

/**
* 流的各种操作
*
* @author jinglv
* @date 2021/7/21 1:33 下午
*/
class StreamOperator {

List<Sku> skus;

@BeforeEach
void init() {
skus = CartService.getCartSKuList();
}

@Test
void test() {
// ……
}
}

打印出购物车商品列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[{
"totalPrice": 4999,
"skuPrice": 4999,
"skuCategory": "ELECTRONICS",
"skuName": "无人机",
"totalNum": 1,
"skuId": 654032
}, {
"totalPrice": 2299,
"skuPrice": 2299,
"skuCategory": "ELECTRONICS",
"skuName": "VR一体机",
"totalNum": 1,
"skuId": 654033
}, {
"totalPrice": 3999,
"skuPrice": 3999,
"skuCategory": "SPORTS",
"skuName": "跑步机",
"totalNum": 1,
"skuId": 654221
}, {
"totalPrice": 79.8,
"skuPrice": 79.8,
"skuCategory": "BOOKS",
"skuName": "Java编程思想",
"totalNum": 1,
"skuId": 654121
}, {
"totalPrice": 149.8,
"skuPrice": 149.8,
"skuCategory": "BOOKS",
"skuName": "Java核心技术",
"totalNum": 1,
"skuId": 654122
}, {
"totalPrice": 299,
"skuPrice": 299,
"skuCategory": "CLOTHING",
"skuName": "睡衣套装",
"totalNum": 1,
"skuId": 654021
}, {
"totalPrice": 1999,
"skuPrice": 1999,
"skuCategory": "CLOTHING",
"skuName": "牛仔裤",
"totalNum": 1,
"skuId": 654022
}]

用单元测试的形式一一演示

常用中间操作

对String中间操作的演示

无状态

filter使用

过滤调不符合断言判断的数据

1
2
3
4
5
6
7
8
9
10
11
/**
* filter使用:过滤调不符合断言判断的数据
*/
@Test
void filterTest() {
skus.stream()
// 过滤图书类的商品
.filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
// 过滤的数据打印出来
.forEach(item -> System.out.println(JSONUtil.parse(item)));
}

执行结果

image-20210721135456849

map使用

将一个元素转换成另一个元素

1
2
3
4
5
6
7
8
9
10
11
/**
* map使用:将一个元素转换成另一个元素
*/
@Test
void mapTest() {
skus.stream()
// 将商品列表转换为商品名称集合
.map(sku -> sku.getSkuName())
// 转换的商品名称打印出来
.forEach(item -> System.out.println(JSONUtil.toJsonStr(item)));
}

执行结果

image-20210721135650043

flatMap使用

将一个对象转换成流

1
2
3
4
5
6
7
8
9
10
/**
* flatMap使用:将一个对象转换成流
*/
@Test
void flatMapTest() {
skus.stream()
// 将商品名称分割为字符流返回
.flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
.forEach(item -> System.out.println(JSONUtil.toJsonStr(item)));
}

执行结果

image-20210721140151043

peek使用

对流进行遍历操作,与forEach类似,但不会销毁流元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* peek使用:对流进行遍历操作,与forEach类似,但不会销毁流元素
* peek和forEach都是遍历操作,有什么区别:
* peek是中间操作,流操作完还是继续可以使用,传递到下一步操作
* forEach是终端操作,流操作完事不可用的
* <p>
* 执行以下代码会发现,peek和forEach交替打印的,并不是peek执行完,在执行forEach,这也是流执行个一个特点:
* 流是惰性执行,只有遇到终端操作,流的执行才会从上到下依次执行
*/
@Test
void peekTest() {
skus.stream()
// sku的名称进行循环打印
.peek(sku -> System.out.println(sku.getSkuName()))
.forEach(item -> System.out.println(JSONUtil.toJsonStr(item)));
}

执行结果

image-20210721140356029

有状态

sorted使用

对流中元素进行排序,可选择自然排序或指定排序规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* sorted使用:对流中元素进行排序,可选择自然排序或指定排序规则
* 无参的是按照默认的自然排序操作排
* <p>
* 执行以下代码会发现,peek会先打印出来,forEach会后打印出来,这是因为在中间加了一个sorted有状态的操作,所有经过peek的数据都要在sorted里面做一个汇总,由sorted统一排序之后再交由下一个环节处理
* 有状态操作和无状态的操作区别,会对数据执行的先后有所影响
*/
@Test
public void sortedTest() {
skus.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach(item -> System.out.println(JSONUtil.toJsonStr(item)));
}

执行结果

image-20210721140522376

可以看到,总价是按从小到大排序的

distinct使用

对流元素进行去重

1
2
3
4
5
6
7
8
9
10
11
/**
* distinct操作:对流元素进行去重,有状态操作
*/
@Test
void distinctTest() {
skus.stream()
.map(Sku::getSkuCategory)
// 过滤有几类商品
.distinct()
.forEach(item -> System.out.println(item));
}

执行结果

image-20210726140530251

skip使用

跳过前N条记录

1
2
3
4
5
6
7
8
9
10
11
/**
* skip使用:跳过前N条记录,有状态操作
*/
@Test
void skipTest() {
skus.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
// 商品根据总价进行排序,并跳过前三条
.skip(3)
.forEach(item -> System.out.println(JSONUtil.parse(item)));
}

执行结果

image-20210726140708830

limit使用

截断前N条记录

1
2
3
4
5
6
7
8
9
10
11
/**
* limit使用:截断前N条记录,有状态操作
*/
@Test
void limitTest() {
skus.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
// 商品根据总价进行排序,并截取前三条
.limit(3)
.forEach(item -> System.out.println(JSONUtil.parse(item)));
}

执行结果

image-20210726140816755

通过skip与limit的功能,可以做一个简单的假的分页功能

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 通过skip与limit的功能,可以做一个简单的假的分页功能
*/
@Test
void skipAndLimitTest() {
skus.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
// 每页3条,页数从0开始,跳过数据
.skip(0 * 3)
.limit(3)
.forEach(item -> System.out.println(JSONUtil.parse(item)));
}

通过修改skip中页数*条数(注:页数从0开始),并截取条数,实现假的分页功能,执行结果:

第一页:

image-20210726141110974

第二页:![image-20210726141149447](/Users/lvjing/Library/Application Support/typora-user-images/image-20210726141149447.png)

总结

以上是对String的中间操作演示示例,对于不同操作有以下总结:

  1. filter使用:过滤调不符合断言判断的数据
  2. map使用:将一个元素转换成另一个元素
  3. flatMap使用:将一个对象转换成流
  4. peek使用:对流进行遍历操作,与forEach类似,但不会销毁流元素
  5. sorted使用:对流中元素进行排序,可选择自然排序或指定排序规则
  6. distinct操作:对流元素进行去重,有状态的操作
  7. skip使用:跳过前N条记录,有状态的操作
  8. limit使用:截断前N条记录,有状态的操作

常用终端操作

短路

allMatch使用

所有元素匹配,返回true

1
2
3
4
5
6
7
8
9
10
11
/**
* allMatch使用:所有元素匹配,返回true,终端操作,短路操作(依次消费流中的数据,只要有一个匹配上就会返回,不会继续消费)。
* 如何查看是短路操作,使用peek打印一下,依次匹配流中数据,只要遇到不匹配则直接返回
*/
@Test
void allMatchTest() {
boolean b = skus.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.allMatch(sku -> sku.getTotalPrice() > 1000);
System.out.println(b);
}

执行结果

image-20210727134408654

anyMatch使用

1
2
3
4
5
6
7
8
9
10
/**
* anyMatch使用:任何元素匹配要匹配,返回true,终端操作,短路操作(依次消费流中的数据,只要有一个匹配上就会返回,不会继续消费)。
*/
@Test
void anyMatchTest() {
boolean b = skus.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.anyMatch(sku -> sku.getTotalPrice() > 1000);
System.out.println(b);
}

执行结果

image-20210727134511515

noneMatch使用

1
2
3
4
5
6
7
8
9
10
/**
* noneMatch使用:任何元素都不匹配返回true
*/
@Test
void noneMatchTest() {
boolean b = skus.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.noneMatch(sku -> sku.getTotalPrice() > 10000);
System.out.println(b);
}

执行结果

image-20210727134613902

findFirst使用

1
2
3
4
5
6
7
8
/**
* findFirst使用:找到第一个, 终端操作,短路操作
*/
@Test
void findFirstTest() {
Optional<Sku> skuOptional = skus.stream().findFirst();
System.out.println(JSONUtil.parse(skuOptional.get()));
}

执行结果

image-20210727134706423

findAny使用

1
2
3
4
5
6
7
8
9
10
11
/**
* findAny使用:找到任何一个
* findAny和findFirst区别:并行上会有区别,找第一个元素在并行上限制会更多一些,任意元素在并行上就会少,在并行上findAny会比findFirst上快
* findAny缺点是会随机匹配到元素
* 流是串行的情况下:findAny和findFirst是没有区别的
*/
@Test
void findAnyTest() {
Optional<Sku> skuOptional = skus.stream().findAny();
System.out.println(JSONUtil.parse(skuOptional.get()));
}

执行结果

image-20210727134801127

非短路

maxTest使用

1
2
3
4
5
6
7
8
9
10
11
12
/**
* max使用:获取流统计的最大值
*/
@Test
void maxTest() {
OptionalDouble optionalDouble = skus.stream()
// 获取总价, mapToDouble将一个元素映射为Double类型
.mapToDouble(Sku::getTotalPrice)
// 获取总价最大的值
.max();
System.out.println(optionalDouble.getAsDouble());
}

执行结果

image-20210727135019252

minTest使用

1
2
3
4
5
6
7
8
9
10
11
12
/**
* min使用:获取流统计的最小值
*/
@Test
void minTest() {
OptionalDouble optionalDouble = skus.stream()
// 获取总价
.mapToDouble(Sku::getTotalPrice)
// 获取总价最小的值
.min();
System.out.println(optionalDouble.getAsDouble());
}

执行结果

image-20210727135135861

count使用

1
2
3
4
5
6
7
8
9
/**
* count使用:获取流中元素的个数
*/
@Test
void countTest() {
// 获取流中元素的个数
long count = skus.stream().count();
System.out.println(count);
}

执行结果

image-20210727135229898

总结

以上是对String的终端操作演示示例,对于不同操作有以下总结:

  1. allMatch使用:所有元素匹配,返回true
  2. anyMatch使用:任何元素匹配要匹配,返回true
  3. noneMatch使用:任何元素都不匹配,返回true
  4. findFirst使用:找到第一个,则返回
  5. findAny使用:找到任何一个
  6. max使用:获取流统计的最大值
  7. min使用:获取流统计的最小值
  8. count使用:获取流中元素的个数

流的构建

  • 由值创建流
  • 由数组创建流
  • 由文件生成流
  • 由函数生成流(无限流)

Stream构建四种形式

由数值直接构建流

1
2
3
4
5
6
7
8
/**
* 由数值直接构建流
*/
@Test
void streamFromValue() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.forEach(System.out::println);
}

执行结果

image-20210728140648799

由数组创建流

1
2
3
4
5
6
7
8
9
/**
* 通过数组构建流
*/
@Test
void streamFroArray() {
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
stream.forEach(System.out::println);
}

执行结果

image-20210728140751521

由文件生成流

1
2
3
4
5
6
7
8
9
10
/**
* 通过文件生成流
*
* @throws IOException io异常
*/
@Test
void streamFromFile() throws IOException {
Stream<String> lines = Files.lines(Paths.get("/Users/.../src/test/java/com/stream/shopping/StreamConstructor.java"));
lines.forEach(System.out::println);
}

注意:替换文件地址

执行结果

image-20210728140901651

由函数生成流(无限流)

由于是无限流,因此使用limit限制一下

Iterate

1
2
3
4
5
6
7
8
9
/**
* 通过函数生成的流(无限流)-- 迭代器方式
*/
@Test
void streamFromFunctionForIterate() {
// 通过迭代的方式生成流, 无限的生成
Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);
iterate.limit(100).forEach(System.out::println);
}

执行结果

image-20210728141446018

Generate

1
2
3
4
5
6
7
8
9
/**
* 通过函数生成的流(无限流)-- 构造器方式
*/
@Test
void streamFromFunctionForGenerate() {
// 通过生成器,随机生成无限的流
Stream<Double> generate = Stream.generate(Math::random);
generate.limit(100).forEach(System.out::println);
}

执行结果

image-20210728141515373