一文搞懂Java中的Stream流操作
# 前言
Stream流操作是Java 8提供一个重要新特性,它允许开发人员以声明式方式处理集合,其核心类库主要改进了对集合类的 API和新增Stream操作。
你还可以把他理解成sql的视图,集合就相当于数据表中的数据,获取Stream流的过程就是确定数据表的属性和元数据的过程,元数据的每一个元素就是表中的数据,对Stream流进行操作的过程就是通过sql对这些数据进行查找、过滤、组合、计算、操作、分组等过程,获取结果就是sql执行完毕之后获取的结果视图一样,深入理解Stream流可以让我们使用更加简洁的代码获取自己想要的数据。
# 1 为什么需要 Stream
Stream 作为 Java 8 的一大亮点,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。
Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。
# 2 Stream流的创建
- 通过集合创建
- 通过数组创建
- 使用Stream的静态方法
Stream和parallelStream的简单区分
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。
- 串行流:适合存在线程安全问题、阻塞任务、重量级任务,以及需要使用同一事务的逻辑。
- 并行流:适合没有线程安全问题、较单纯的数据处理任务。
# 3 Stream的API
归类
# 4 使用
使用前我们需要知道两个东西,一个是Optional,另外一个就是函数式接口
Optional:Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
作用:
- 它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常
函数式接口:有且仅有一个抽象方法的接口,通过@FunctionalInterface
注解声明
作用:
是Lambda表达式的使用前提
概念层面,为了表示接口就代表这个抽象方法,所以将名起为函数式接口
相信大家都了解,或多或少都用过,这里不做过多阐述。进入正题,结合日常开发遇到的一些场景,整理如下一些Stream使用场景。
构建一个产品List如下:
@Data
@AllArgsConstructor
public class Product {
private Integer id;
private String name;
private String desc;
/**
* 价格
*/
private BigDecimal price;
/**
* 类目
*/
private String category;
/**
* 标签
*/
private List<String> labels;
}
private static final List<Product> products = new ArrayList<>();
static {
List<String> labels0 = new ArrayList<>();
List<String> labels1 = Arrays.asList("Java","技术书籍");
List<String> labels2 = Arrays.asList("男士","冬装");
List<String> labels3 = Arrays.asList("男士","潮流");
products.add(new Product(1,"苹果笔记本","电脑",new BigDecimal(13559),"办公",labels0));
products.add(new Product(2,"深入了解Java虚拟机","",new BigDecimal(89),"书籍",labels1));
products.add(new Product(3,"戴尔笔记本","电脑",new BigDecimal(4559),"办公",labels0));
products.add(new Product(4,"男士外套","",new BigDecimal(210),"服装",labels2));
products.add(new Product(5,"四叶草手链","",new BigDecimal(1500),"首饰",labels0));
products.add(new Product(6,"男士短裤","",new BigDecimal(59),"服装",labels2));
products.add(new Product(7,"方便面","",new BigDecimal(50),"零食",labels0));
products.add(new Product(8,"三只松鼠","",new BigDecimal(99),"零食",labels0));
products.add(new Product(9,"百草味","",new BigDecimal(118),"零食",labels0));
products.add(new Product(10,"男士板鞋","",new BigDecimal(269),"鞋子",labels3));
products.add(new Product(11,"方便面","",new BigDecimal(50),"零食",labels0));
}
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
# 4.1 遍历
遍历Stream中的每一个元素
//遍历
products.stream().forEach(e->System.out.println(e.getName()));
products.stream().peek(e->System.out.println(e.getName()));
2
3
# 4.2 匹配
Stream 有两个find方法,三个 match 方法:
- findFirst: Stream 的第一个,可配合filter等使用
- findAny: Stream中任意一个,可配合filter等使用
- allMatch:Stream 中全部元素符合传入的 predicate,返回 true
- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
//匹配查找
Optional<Product> firstPro = products.stream().findFirst();
//匹配任意一个
Optional<Product> anyPro = products.stream().findAny();
//匹配id>4
boolean match = products.stream().anyMatch(p->p.getId()>4);
2
3
4
5
6
# 4.3 过滤
通过传入断言匹配每一个元素,返回符合匹配的新的Stream流,也是最常用的方法之一。
//过滤描述为空的产品
List<Product> productList = products.stream().filter(
item -> StringUtils.hasLength(item.getDesc()))
.collect(Collectors.toList());
//过滤取其中的一个
Optional<Product> pro = products.stream().filter(
item -> StringUtils.hasLength(item.getDesc())).findAny();
//过滤取其中的第一个
pro = products.stream().filter(item-> StringUtils.hasLength(item.getDesc())).findFirst();
2
3
4
5
6
7
8
9
10
11
# 4.4 收集
//统计每一类产品的数量
Map<String,Long> catMap = products.stream().collect(
Collectors.groupingBy(Product::getCategory, Collectors.counting()));
//找出商品中价格最大的
Optional<Product> maxPricePro = products.stream().collect(
Collectors.maxBy(Comparator.comparing(Product::getPrice)));
Optional<Product> maxPrice = products.stream().max(
Comparator.comparing(Product::getPrice));
//统计所有商品价格
double sumPricePro = products.stream().collect(
Collectors.summarizingDouble(e -> e.getPrice().doubleValue())).getSum();
//转换为Map<分类,产品>
Map<String, Product> productCatMap = products.stream().collect(
Collectors.toMap(Product::getCategory, Function.identity(), (o, n) -> n));
//转换为Set
Set<Product> proSet = products.stream().collect(Collectors.toSet());
//根据某个属性去重
products.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(()->new TreeSet<>(
Comparator.comparing(n->n.getName()))),ArrayList::new));
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
# 4.5 聚合
求最大值、最小值、计数,min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现
//找出商品中价格最大的
Optional<Product> maxPrice = products.stream().max(
Comparator.comparing(Product::getPrice));
//找出商品中价格最小的
Optional<Product> minPrice = products.stream().min(
Comparator.comparing(Product::getPrice));
long proSize = products.stream().count();
//价格最高的商品
maxPricePro = products.stream().reduce(
(p1, p2) -> p1.getPrice().compareTo(p2.getPrice())>0?p1:p2);
2
3
4
5
6
7
8
9
10
11
12
13
# 4.6 映射
映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
- flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
//获取所有分类
Set<String> cats = products.stream().map(
p -> p.getCategory()).collect(Collectors.toSet());
//获取所有标签
Set<String> labels = products.stream().map(
p -> p.getLabels()).flatMap(Collection::stream).collect(Collectors.toSet());
2
3
4
5
6
7
# 4.7 排序
这个方法的主要作用是对流元素按一定规则进行排序,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
//按产品id排序
List<Product> orderAscList = products.stream().sorted(
Comparator.comparing(Product::getId))
.collect(Collectors.toList());
//按产品id降序
List<Product> orderDescList = products.stream().sorted(
Comparator.comparing(Product::getId, Comparator.reverseOrder()))
.collect(Collectors.toList());
//前6条按产品id降序
orderDescList = products.stream().limit(6).sorted(
Comparator.comparing(Product::getId, Comparator.reverseOrder()))
.collect(Collectors.toList());
2
3
4
5
6
7
8
9
10
11
12
13
14
# 总结
本文主要讲了Java中的Stream流的功能介绍,API分类以及常用的操作,包含遍历、匹配、过滤、收集、聚合、映射、排序等代码示例。适当的使用Stream流可以让我们代码更加高效简洁。