Stream API的操作

前沿

在使用的jdk8这些新的语法和API的时候,一直对Stream的操作,一直是边搜索边使用,还经常忘记其使用方式,现特别总结一下。

其实使用Stream有时候有类似于sql的思想,想着怎么过滤数据和转换数据,最后输出一个自己想要的格式即可。但是我们要先知道jdk提供了什么方法

Stream的API的使用,我们将其分为几个重点

1. 创建Stream

1.1 Arrays.stream(Array) 和 Stream.of(Array)

这两个我个人认为都是将数组转为Stream,所以直接放在一起了

1
2
3
Integer[] arr  = new Integer[]{1,2,3,4,55,6,7};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream = Stream.of(arr);

1.2 Collection.stream()

这是用的最多的转换方式,Collection的子类都可以直接使用.stream()来获取Stream

1.3 Stream.generate()

Stream.generate() 可以传入一个参数函数,用的倒是不多

1
2
3
Stream<String> stream = Stream.generate(() -> "test").limit(3);
String[] strArr = stream.toArray(String[]::new);
System.out.println(Arrays.toString(strArr));

2. Stream的转换

Stream的转换主要是将Stream转换成一个新的Stream。

2.1 filter

从名字可以看到实际就是一个Stream的过滤转换,可以变成一个新的Stream。

1
2
Integer[] a = new Integer[]{2,342,13,21,4,14,23431};
List<Integer> collect = Stream.of(a).filter(d -> d > 5).collect(Collectors.toList());

2.2 map 和 flatMap

map方法其主要是转换Stream的类型,同样传入一个参数函数,需要传递一个转换的函数即可

1
2
3
// 这里从Integer的Stream 转换成了String类型
Integer[] a = new Integer[]{2,342,13,21,4,14,23431};
Stream<String> objectStream = Stream.of(a).map(i -> String.valueOf(i));

flatMap方法主要是将多个Stream的合并操作

1
2
3
4
5
6
7
Integer[] a = new Integer[]{2,342,13,21,4,14,23431};
Integer[] b = new Integer[]{3,4,5,6,7,7};
List<Integer[]> arr = new ArrayList<>();
arr.add(a);
arr.add(b);
// 多个Stream转为一个
List<Integer> collect = arr.stream().flatMap(i -> Arrays.stream(i)).collect(Collectors.toList());

2.3 limit方法和skip方法

这两个比较好理解

limit方法就是截取前n个元素的流,若长度不够则返回原始流

1
2
3
Integer[] a = new Integer[]{2,342,13,21,4,14,23431};
List<Integer> collect = Stream.of(a).limit(20).collect(Collectors.toList());
collect.forEach(System.out::println);

skip方法和limit相反,就是丢弃前n个元素

1
2
3
Integer[] a = new Integer[]{2,342,13,21,4,14,23431};
List<Integer> collect = Stream.of(a).limit(4).collect(Collectors.toList());
collect.forEach(System.out::println);

2.4 distinct方法和sorted方法

distinct好理解,和sql的数据类似,其实就是去除重复的,如果是实体需要注意重写equalshashCode

1
2
3
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
List<Integer> collect = Stream.of(a).distinct().collect(Collectors.toList());
collect.forEach(System.out::println);

sorted也比较好理解,就是对流进行排序,和Collections.sort()有点类似

1
2
3
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
List<Integer> collect = Stream.of(a).sorted().collect(Collectors.toList());
collect.forEach(System.out::println);

3. 聚合操作

Stream的聚合就是将Stream汇聚和计算出一个具体的数值,比如最大值、最小值、总数等。这部分相对比较好理解

3.1 min、max、count

1
2
3
4
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
Integer min = Stream.of(a).min(Integer::compareTo).get();
Integer max = Stream.of(a).max(Integer::compareTo).get();
long count = Stream.of(a).count();

3.2 findFirst 和 findAny

获取第一个元素,我们可以用过fiter来获取第一个满足条件的元素

1
2
3
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
Integer integer = Stream.of(a).filter(i -> i > 3).findFirst().get();
System.out.println(integer);

findAny 主要是在并行stream中使用,parallelStream的时候常用,这里不做例子了。

3.3 anyMatch、 allMatch、noneMatch

anyMatch 可以判断流中是否有匹配的数据,然后返回boolean

allMatch 判断所有元素是否匹配

noneMatch 判断没有元素匹配

1
2
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
boolean boo = Stream.of(a).anyMatch(i -> i == 14);

3.4 reduce

reduce 算是用的比较多的,可以将流中数据进行深沉次的计算

1
2
3
4
5
6
7
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
// 求和
Integer integer = Stream.of(a).reduce((x, y) -> x + y).get();
System.out.println(integer);
// 带有初始化的求和
integer = Stream.of(a).reduce(2,(x, y) -> x + y);
System.out.println(integer);

4. 收集输出操作

我们将list转换成stream后,不会总是聚合操作,大部分我们还是返回一个集合回来,所以有了这一步的收集操作。将Stream重新变成一个Collection来便于我们使用

4.1 集合输出

集合输出还比较简单,常规的有 list、Set

1
2
3
4
5
Integer[] a = new Integer[]{2,2,342,13,21,4,14,23431};
List<Integer> array = Stream.of(a).collect(Collectors.toList());
List<Integer> array1 = Stream.of(a).collect(Collectors.toCollection(ArrayList::new));
Set<Integer> hashSet = Stream.of(a).collect(Collectors.toSet());
Set<Integer> hashSet1 = Stream.of(a).collect(Collectors.toCollection(HashSet::new));

4.2 输出到map

输出到map。经常是范型是一个对象的时候使用,也是非常常用的写法

先来创建和一个简单示例例子

1
2
3
4
5
6
7
# NameMode 实际就是一个对象而已,不在贴出
List<NameModel> list = new ArrayList<>();
list.add(new NameModel("23",2));
list.add(new NameModel("das",3));
list.add(new NameModel("eqw",4));
list.add(new NameModel("fw",5));
list.add(new NameModel("fw3123",5));

key和value都是具体的字段

1
2
Map<Integer, String> collect =
list.stream().collect(Collectors.toMap(NameModel::getName, NameModel::getName));

key是具体的字段,value为实体

1
2
Map<Integer, NameModel> collect =
list.stream().collect(Collectors.toMap(NameModel::getName, Function.identity()));

还有一种情况是当key值重复多的时候。会出现异常。系统无法通过key值来获取唯一的value。这时候就会有第三个参数来处理了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这里会报错
Map<Integer, String> collect =
list.stream().collect(Collectors.toMap(NameModel::getAge, NameModel::getName));
// 通过第三个参数来解决
Map<Integer, String> collect =
list.stream().collect(
Collectors.toMap(
NameModel::getAge,
NameModel::getName,
// 随便返回一个参数了
(newValue,oldValue) -> {
return newValue;
},
// 这里也可以返回指定的map
TreeMap::new
));

4.3 分组输出

1
2
3
4
// 根据 age 来映射多个集合
Map<Integer, List<NameModel>> collect = list.stream().collect(Collectors.groupingBy(NameModel::getAge));
// 分组的衍生,返回分组后的数量
Map<Integer, Long> collect = list.stream().collect(Collectors.groupingBy(NameModel::getAge, Collectors.counting()));

4.4 拼接字符

1
2
3
Integer[] arr = new Integer[]{2,2,342,13,21,4,14,23431};
String str = Stream.of(arr).map(String::valueOf).collect(Collectors.joining(","));
System.out.println(str);

5. 分组

我们可以把它想象成sql中的group by操作,就好理解很多了

5.1 groupingBy

1
2
3
4
5
6
7
8
9
10
11
12
13
// 分组后的数据
Map<Integer, List<NameModel>> map =
list.stream().collect(Collectors.groupingBy(NameModel::getAge));
// 分组后求分组的count
Map<Integer, Long> count =
list.stream().collect(Collectors.groupingBy(NameModel::getAge, Collectors.counting()));
// 分组后 求和
Map<String, Integer> sum =
list.stream().collect(Collectors.groupingBy(NameModel::getName, Collectors.summingInt(NameModel::getAge)));
// 分组后 获取最大的一条数据, MinBy 可以获取最好的一条
Map<String, Optional<NameModel>> maxBy =
list.stream().collect(Collectors.groupingBy(NameModel::getName,
Collectors.maxBy(Comparator.comparing(NameModel::getAge))));

上部分的操作其实和sql分组思想都是类似。毕竟我们想要的数据变化方式一般也就这些

也可以支持多级分组

1
2
3
// 可以无限套娃,自己去试试吧
Map<String, Map<Integer, List<NameModel>>> collect =
list.stream().collect(Collectors.groupingBy(NameModel::getName, Collectors.groupingBy(NameModel::getAge)));

5.2 partitioningBy

partitioningBy主要是获取boolean类型的两组数据。当然对分组后的数据进行各种求和,求max操作都是和groupBy一样支持的。

1
2
Map<Boolean, List<NameModel>> collect = 
list.stream().collect(Collectors.partitioningBy(nameModel -> nameModel.getAge() == 4));

6. 并行操作

并行流在stream的操作上还是比较好操作

1
Stream.of(roomList).parallel();

直接使用其代码转换一下Stream流即可,但是注意后面的操作都是并行,需要注意并发安全。