Stream流
一. 什么是Stream?
Stream流代表着一系列元素的序列,并且支持许多并行操作。它不是一种数据结构,而是一种用于处理数据的工具。通过使用Stream,我们可以轻松地对集合进行筛选、排序、聚合等操作,而无需手动编写传统的循环代码。
二. 创建Stream流
- 可以通过集合调用stream()方法来获取一个Stream对象。
List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> stream = list.stream();
- 可以使用Stream.of()方法创建一个包含指定元素的Stream流
Stream<String> stream = Stream.of("apple", "banana", "orange");
- 还可以使用Arrays.stream()方法创建流
String[] strings = {"apple", "banana", "orange"};
Stream<String> stream = Arrays.stream(strings);
我们最长用的就是使用集合对象调用stream()方法创建流。
三. 如何使用Stream?
对于使用Java 8中的Stream流,通常可以分为三个步骤:开启Stream操作、中间操作和终端操作。
-
开启Stream操作:首先需要有一个数据源,比如集合或数组,然后通过调用其
stream()
方法或者使用Stream.of()
等方式来创建一个Stream对象,这就是开启Stream操作的第一步。这一步已经在上述介绍过。 -
中间操作:在得到了Stream对象之后,可以对其进行中间操作,比如过滤(
filter
)、映射(map
)、去重(distinct
)等操作。这些操作会返回一个新的Stream对象,允许我们可以连接多个中间操作从而形成一个操作链。 -
终端操作:最后,在进行完所有需要的中间操作之后,可以进行终端操作来触发实际的遍历计算,产生一个最终的结果。常见的终端操作包括遍历输出(
forEach
)、收集结果(collect
)、规约操作(reduce
)等。
因此,整个Stream流的处理过程可以看作是一个流水线式的操作:首先是开启Stream,然后通过连接多个中间操作来对数据进行处理,最终通过终端操作得到最终的结果。
四. 常用的Stream的中间操作
中间操作的方法返回值是Stream,所以可以实现链式调用,允许我们可以连接多个中间操作从而形成一个操作链。这些方法可以帮助我们对数据进行筛选、转换、去重、排序等操作,而不修改原始数据源。
- filter():返回符合条件的元素组成的新Stream。
- map():对Stream中的每个元素执行指定的操作,返回操作后结果组成的新Stream。
- distinct():去除Stream中的重复元素。
- sorted():对Stream中的元素进行排序。
- peek():在执行Stream操作过程中,可以进行一些副作用操作,例如,打印元素等。
- limit():对Stream中的元素进行截取,只返回前面n个元素。
- skip():对Stream中的元素进行跳过,只返回后面的元素。
五. 常用的Stream的终端操作
终端操作的方法返回值不是Stream类型的,可以有返回值,也可以无返回值。
- forEach:对Stream中的每个元素执行指定的操作。
- collect:将Stream中的元素收集到一个集合中,比如List、Set、Map等。
这里着重说一下collect()这个方法,方法参数通常借用java.util.stream.Collectors这个类中的静态方法,以下是常用的Collectors方法:
方法名 | 描述 |
---|---|
toList | 将流中的数据重新归集到新的List集合里 |
toSet | 将流中的数据重新归集到新的Set集合里,它会自动去除重复值 |
toMap | 将流中的数据重新归集到新的Map集合里,需要手动指定键值对 |
counting | 计算流中的数据总数并返回 |
partitioningBy | 根据指定条件分为两个Map集合,一个是符合条件,另一个为不符合条件的 |
groupingBy | 根据指定属性分为多个Map集合,具体根据所指定的属性有多少种 |
joining | 将stream中的元素用指定的字符连接一个成一个字符串 |
reducing | 相比于Stream接口中的reduce方法,增加了对自定义的支持。 |
- reduce:通过指定的函数来将Stream中的元素规约成一个值。
- count:返回Stream中的元素个数。
- anyMatch/allMatch/noneMatch:用于检查Stream中是否存在满足条件的元素。
- findAny/findFirst:返回Stream中的任意一个或第一个元素。
- min/max:返回Stream中的最小值或最大值。
六. 实操
接下俩我们举例实现这些方法,这里我们直接拿自定义类的List集合作为演示,不再使用数值型的集合举例。
自定义一个Material类,并手动模拟创建一个List集合:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Material {
//id
private String id;
//材料名字
private String name;
//材料类型
private String type;
//材料规格
private String category;
//材料单位
private String unit;
//材料数量
private BigDecimal quantity;
}
现有一个这样的集合如下:
Material material1 = new Material("1","电缆1号","电缆类","直径2mm","m",new BigDecimal(100));
Material material2 = new Material("2","电缆2号","电缆类","直径3mm","km",new BigDecimal(80));
Material material3 = new Material("3","电缆3号","电缆类","直径5mm","m",new BigDecimal(50));
Material material4 = new Material("4","电缆1号","电缆类","直径2mm","m",new BigDecimal(200));
Material material5 = new Material("5","氧气","气体类","1L","瓶",new BigDecimal(150));
Material material6 = new Material("6","氮气","气体类","500mL","瓶",new BigDecimal(500));
Material material7 = new Material("7","氦气","气体类","500mL","瓶",new BigDecimal(200));
List<Material> materials = Arrays.asList(material1, material2, material3, material4, material5, material6, material7);
后续演示都是在这个materials集合下操作的。
例一
筛选出类型为"气体类"的元素并构成一个新的list集合:
List<Material> gases = materials.stream().filter(e -> e.getType().equals("气体类")).collect(Collectors.toList());
System.out.println(gases);
结果:
[Material(id=5, name=氧气, type=气体类, category=1L, unit=瓶, quantity=150), Material(id=6, name=氮气, type=气体类, category=500mL, unit=瓶, quantity=500), Material(id=7, name=氦气, type=气体类, category=500mL, unit=瓶, quantity=200)]
例二
获取符合条件type="电缆类"和name="电缆1号"的第一个元素:
List<Material> collect = materials.stream().filter(e -> e.getType().equals("电缆类") && e.getName().equals("电缆1号")).collect(Collectors.toList());
System.out.println(collect.get(0));
Optional<Material> first = materials.stream().filter(e -> e.getType().equals("电缆类") && e.getName().equals("电缆1号")).findFirst();
System.out.println(first.get());
这两种方式都可以的,后面使用了findFirst()方法,结果如下:
Material(id=1, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=100)
Material(id=1, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=100)
例三
按照数量quantity大于等于200的条件使用partitioningBy分成两个Map集合:
Map<Boolean, List<Material>> map = materials.stream().collect(Collectors.partitioningBy(e -> e.getQuantity().compareTo(new BigDecimal(200)) >= 0));
System.out.println(map.entrySet());
打印结果如下:
[false=[Material(id=1, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=100), Material(id=2, name=电缆2号, type=电缆类, category=直径3mm, unit=km, quantity=80), Material(id=3, name=电缆3号, type=电缆类, category=直径5mm, unit=m, quantity=50), Material(id=5, name=氧气, type=气体类, category=1L, unit=瓶, quantity=150)], true=[Material(id=4, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=200), Material(id=6, name=氮气, type=气体类, category=500mL, unit=瓶, quantity=500), Material(id=7, name=氦气, type=气体类, category=500mL, unit=瓶, quantity=200)]]
红色字体为不符合条件的map集合,绿色字体为符合条件的map集合
例四
按照类型type字段使用groupingBy分组多个map集合,map集合的数量取决于type的种类数:
Map<String, List<Material>> typeMap = materials.stream().collect(Collectors.groupingBy(Material::getType));
System.out.println(typeMap.entrySet());
输出结果(红色为气体类,绿色为电缆类):
[气体类=[Material(id=5, name=氧气, type=气体类, category=1L, unit=瓶, quantity=150), Material(id=6, name=氮气, type=气体类, category=500mL, unit=瓶, quantity=500), Material(id=7, name=氦气, type=气体类, category=500mL, unit=瓶, quantity=200)], 电缆类=[Material(id=1, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=100), Material(id=2, name=电缆2号, type=电缆类, category=直径3mm, unit=km, quantity=80), Material(id=3, name=电缆3号, type=电缆类, category=直径5mm, unit=m, quantity=50), Material(id=4, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=200)]]
例五
将原集合的id属性使用map()方法组成一个新的流,最终使用Collectors的joining方法,用","(逗号)连接形成一个字符串:
String ids = materials.stream().map(Material::getId).collect(Collectors.joining(","));
System.out.println(ids);
输出结果:
1,2,3,4,5,6,7
例六
先筛选出type="气体类"的元素,然后将其转换成以name为key,quantity的map集合:
Map<String, BigDecimal> gasMap = materials.stream().filter(e -> e.getType().equals("气体类")).collect(Collectors.toMap(Material::getName, Material::getQuantity));
System.out.println(gasMap.entrySet());
输出结果:
[氧气=150, 氦气=200, 氮气=500]
例七
现在有一个这样的需求,就是name,type,category,unit四个属性相同的时候将其合并起来,使得其quantity累加起来。
Map<String, BigDecimal> materialMap = materials.stream().collect(Collectors.toMap(e -> e.getName() + "," + e.getType() + "," + e.getCategory() + "," + e.getUnit(),
Material::getQuantity, BigDecimal::add));
materialMap.entrySet().forEach(System.out::println);
输出结果为:
氦气,气体类,500mL,瓶=200
电缆2号,电缆类,直径3mm,km=80
氧气,气体类,1L,瓶=150
电缆1号,电缆类,直径2mm,m=300
氮气,气体类,500mL,瓶=500
电缆3号,电缆类,直径5mm,m=50
因为我们提供的数据只有以下两种材料的四个属性值是相同的所以输出结果符合预期:
例八
接下来我们熟悉一下Function.identity()这个方法,这个方法的意思是:返回一个总是返回其输入参数的函数。也就是说你给的参数是什么,它就将参数本身再重新返回。
现有这样的需求,将原集合转成一个以id为key,以自身元素为value的一个集合,代码如下:
Map<String, Material> map1 = materials.stream().collect(Collectors.toMap(Material::getId, Function.identity()));
map1.entrySet().forEach(System.out::println);
其输出结果为:
1=Material(id=1, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=100)
2=Material(id=2, name=电缆2号, type=电缆类, category=直径3mm, unit=km, quantity=80)
3=Material(id=3, name=电缆3号, type=电缆类, category=直径5mm, unit=m, quantity=50)
4=Material(id=4, name=电缆1号, type=电缆类, category=直径2mm, unit=m, quantity=200)
5=Material(id=5, name=氧气, type=气体类, category=1L, unit=瓶, quantity=150)
6=Material(id=6, name=氮气, type=气体类, category=500mL, unit=瓶, quantity=500)
7=Material(id=7, name=氦气, type=气体类, category=500mL, unit=瓶, quantity=200)
总结
Stream流为我们提供了一种更加便捷和高效的处理集合数据的方式,它的引入使得我们能够以一种声明性的方式对集合进行操作。通过本文的介绍,相信您已经对Stream流有了初步的了解,希望你能够在实际项目中应用Stream流,在处理集合数据时事半功倍。