深入了解Java 8 新特性:Stream流的实践应用(二)
阅读建议
嗨,伙计!刷到这篇文章咱们就是有缘人,在阅读这篇文章前我有一些建议:
- 本篇文章大概8000多字,预计阅读时间长需要10分钟(不要害怕字数过多,其中有一大部分是示例代码,读起来是比较轻松的)。
- 本篇文章兼具实战性和理论性,是一篇质量分数较高的技术干货文章,建议收藏起来,方便时常学习与回顾,温故而知新。
- 创作不易,免费的点赞、关注,请走上一走,算是对博主一些鼓励,让我更有动力输出更多的干货内容。
Collectors类的核心方法
Stream是一个数据结构,它提供了一种方便的方式来处理和操作数据,Stream.collect() 方法则用于将 Stream 中的元素收集到指定的收集器(Collector)中,返回一个结果对象。收集器(Collector)可以是任何实现了 Collector 接口的对象,用于定义如何将 Stream 中的元素收集到结果对象中。而Collectors可以看作是收集器(Collector)的生产工厂,可以调用具体的静态方法返回一个具体的收集器。常用的如下:
Collectors#toCollection
Collectors类的toCollection()方法用于将Stream中的元素收集到新的集合中。这个方法的功能作用是接收一个输入流并产生一个输出结果,即把Stream中的元素收集到某种类型的集合中,这里的某类类型集合可由用户以lambda表达式来定义,如:
- 收集元素到ArrayList:通过调用toCollection()方法和传递ArrayList类型的构造方法,可以将Stream中的元素收集到ArrayList中。
- 收集元素到HashSet:通过调用toCollection()方法和传递HashSet类型的构造方法,可以将Stream中的元素收集到HashSet中。
- 收集元素到LinkedHashSet:通过调用toCollection()方法和传递LinkedHashSet类型的构造方法,可以将Stream中的元素收集到LinkedHashSet中。
- 收集元素到TreeSet:通过调用toCollection()方法和传递TreeSet类型的构造方法,可以将Stream中的元素收集到TreeSet中。
- 收集元素到List或Set:通过调用toCollection()方法和传递适当的集合类型构造方法,可以将Stream中的元素收集到List或Set中。
示例
@Test
public void test22(){
Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu");
HashSet<String> set = stream.collect(Collectors.toCollection(() -> new HashSet<String>()));
System.out.println(set.toString());
}
Collectors#toList
Collectors.toList() 用于将 Stream 中的元素收集到一个新的 List 中。这个方法返回一个 Collector 对象,该对象定义了如何将 Stream 中的元素收集到 List 中。
Collectors#toUnmodifiableList
Collectors.toUnmodifiableList() 用于将 Stream 中的元素收集到一个新的不可修改的 List 中。这个方法返回一个 Collector 对象,该对象定义了如何将 Stream 中的元素收集到一个不可修改的 List 中。与Collectors#toList区别就是返回的 List 是不可修改的,无法被重新赋值或添加/删除元素。
Collectors#toSet
Collectors.toSet() 用于将 Stream 中的元素收集到一个新的 Set 中。这个方法返回一个 Collector 对象,该对象定义了如何将 Stream 中的元素收集到 Set 中。
需要注意的是,Collectors.toSet() 方法返回的 Collector 对象可以处理任意类型的 Stream,包括泛型 Stream。这意味着您可以使用该方法将任何类型的元素收集到 Set 中。另外,由于 Set 本身不允许存在重复的元素,因此 Collectors.toSet() 方法会自动去重,只保留唯一的元素。
Collectors#toUnmodifiableSet
Collectors.toUnmodifiableSet()用于将 Stream 中的元素收集到一个新的、不可修改的 Set 中。这个方法返回一个 Collector 对象,该对象定义了如何将 Stream 中的元素收集到一个不可修改的 Set 中。
需要注意的是,由于返回的 Set 是不可修改的,因此不能添加或删除元素。这可以确保收集到的元素不会在后续操作中被修改。此外,与 Collectors.toList() 方法类似,toUnmodifiableSet() 方法也可以处理任意类型的 Stream,包括泛型 Stream。
Collectors#joining
Collectors.joining() 方法用于连接多个元素。该方法返回一个 Collector 实例,方便在流收集器上进行链式操作。Collectors.joining() 方法以遭遇元素的顺序拼接元素。我们可以传递自定义的拼接字符串、前缀和后缀。
@Test
public void test23() {
Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu");
String string = stream.collect(Collectors.joining("-"));
System.out.println(string);//输出结果:zhangsan-lisi-wangwu-zhaoliu
}
Collectors#counting
Collectors.counting()用于计算Stream中元素的数量。它会遍历输入的Stream,对每个元素进行计数,并返回一个Long类型的值,表示元素的数量。
@Test
public void test24() {
Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu");;
Long count = stream.filter(item->item.startsWith("a")).collect(Collectors.counting());
System.out.println(count);
}
Collectors#minBy
Collectors.minBy()用于在Stream中的元素上执行最小值计算操作。该方法方法接受一个比较器作为参数,并将该比较器用于Stream中的元素。它会计算每个元素的最小值,并返回一个包含最小值元素的Optional对,Optional对象包装了最小值元素。如果Stream为空,则返回的Optional对象为空(empty)。
@Test
public void test25() {
Stream<Integer> stream = Stream.of(1, 6, 29, 45, 2);
Integer min = stream.collect(Collectors.minBy((v1, v2) -> {
if (v1 > v2) {
return 1;
} else if (v1 < v2) {
return -1;
} else {
return 0;
}
})).get();
System.out.println(min);//输出结果为1
}
Collectors#maxBy
Collectors.maxBy()用于将Stream中的元素进行比较并找出最大值。该方法接受一个比较函数作为参数,该比较函数定义了如何比较Stream中的元素。这个比较函数可以是自定义的,例如比较两个字符串的长度、比较两个日期的日期值等,通过比较操作可以找出Stream中最大的元素。
@Test
public void test26() {
Stream<Integer> stream = Stream.of(1, 6, 29, 45, 2);
Integer min = stream.collect(Collectors.minBy((v1, v2) -> {
if (v1 > v2) {
return -1;
} else if (v1 < v2) {
return 1;
} else {
return 0;
}
})).get();
System.out.println(min);//输出结果为45
}
Collectors#summingInt
Collectors.summingInt()用于将Stream中的元素进行求和操作。该方法的功能作用是接受一个将对象映射为int的函数,并返回一个收集器,用于计算元素的和。在传递给普通的collect方法后,该收集器即执行所需的汇总操作,计算元素的和。
@Test
public void test26() {
Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50);
Integer sum = stream.collect(Collectors.summingInt(item -> item));
System.out.println(sum);//输出结果为150
}
Collectors#averagingInt
Collectors.averagingInt()方法的功能作用是计算数值的平均数。它接受一个将对象映射为求和所需的int的函数,并返回一个收集器。在传递给普通的collect方法后,该收集器即执行所需的汇总操作,计算元素的平均值。
@Test
public void test27() {
Stream<Integer> stream = Stream.of(2, 4, 6);
double sum = stream.collect(Collectors.averagingInt(item->item));
System.out.println(sum);//输出结果为4.0
}
Collectors#reducing
Collectors.reducing()方法的功能作用是对Stream中的元素进行归约操作,将Stream中的元素聚合为单一的值。该方法接受一个BinaryOperator作为参数,用于指定如何将两个元素进行归约操作。
@Test
public void test28() {
Stream<Integer> stream = Stream.of(2, 4, 6);
Integer sum = stream.collect(Collectors.reducing(Integer::sum)).get();
System.out.println(sum);
Stream<Integer> stream2 = Stream.of(1, 2, 3);
Optional<Integer> optional = stream2.collect(Collectors.reducing((v1, v2) -> v1 + v2));
Integer sum2 = optional.get();
System.out.println(sum2);
}
Collectors#groupingBy
Collectors.groupingBy()用于将Stream中的元素进行分组操作。具体来说,先通过传递一个分类函数作为参数,Collectors.groupingBy()方法可以将Stream中的元素按照该函数的返回值进行分组。这个分类函数接受一个元素作为输入,并返回一个用于分组的键。然后Collectors.groupingBy()方法返回一个Collector对象,该对象定义了如何将分组后的结果收集到一个Map中。默认情况下,它会收集每个分组的元素并返回一个Map,其中键是分类函数的返回值,值是对应的元素列表。
@Test
public void test29(){
List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19));
Map<String, List<Student>> map = list.stream().collect(Collectors.groupingBy(item -> item.getName()));
System.out.println(map.toString());
}
Collectors#groupingByConcurrent
Collectors#groupingByConcurrent方法与Collectors#groupingBy方法功能类似,唯一有些区别就是,Collectors#groupingByConcurrent方法返回值是ConcurrentMap
@Test
public void test30(){
List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19));
ConcurrentMap<String, List<Student>> concurrentMap = list.stream().collect(Collectors.groupingByConcurrent(item -> item.getName()));
System.out.println(concurrentMap.toString());
}
Collectors#partitioningBy
Collectors.partitioningBy()用于将Stream中的元素进行分区操作。该方法的功能作用是根据给定的二元函数将Stream中的元素分成两个部分,并返回一个包含两个子列表的Map。
示例
根据姓名是否以z开头对姓名集合进行分区
@Test
public void test31(){
Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu", "zhaoliu");
Map<Boolean, List<String>> map = stream.collect(Collectors.partitioningBy(item -> item.startsWith("z")));
System.out.println(map.toString());//输出结果:{false=[lisi, wangwu], true=[zhangsan, zhaoliu]}
}
Collectors#toMap
Collectors.toMap()用于将Stream中的元素收集到Map中,它接受两个函数作为参数,一个用于映射键,另一个用于映射值。这些函数将应用于Stream中的每个元素,以生成对应的键和值。在收集元素时,如果存在重复的键,Collectors.toMap()方法的行为取决于是否提供了合并函数。如果提供了合并函数,该函数将用于处理重复键,以决定如何合并这些键的值。如果没有提供合并函数,那么在遇到重复键时,则会抛出IllegalStateException异常。
示例
把学生信息的List集合,转化为一个Map,key:学生姓名,value:学生年龄
@Test
public void test32(){
List<Student> list = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19));
Map<String, Integer> map = list.stream().collect(Collectors.toMap(item -> item.getName(), item -> item.getAge()));
System.out.println(map.toString());//输出结果:{lisi=19, zhangsan=18}
List<Student> list2 = Arrays.asList(new Student("zhangsan", 18), new Student("lisi", 19),new Student("lisi", 20));
Map<String, Integer> map2 = list2.stream().collect(Collectors.toMap(item -> item.getName(), item -> item.getAge(), (v1, v2) -> {
return v2;//如果key发生重复,则取重复对象的最后一个对象的val
}));
System.out.println(map2);
}
Collectors#toUnmodifiableMap
Collectors.toUnmodifiableMap()用于将Stream中的元素收集到一个不可修改的Map中。它接受一个二元函数作为参数,用于将Stream中的元素映射为Map中的键值对。返回的Map是不可修改的,即无法添加、删除或修改其中的元素。这样可以保证Map的安全性,防止意外修改原始数据。除了这些特性,在用法上与Collectors#toMap相同
Stream使用过程特别需要注意的几个坑
在使用Java的Stream流时,有一些容易踩坑的地方需要注意:
- 空指针异常:在使用Stream时,需要注意避免空指针异常。例如,在使用filter方法过滤列表时,如果列表为空,会导致空指针异常。因此,在使用Stream之前,需要先判断数据是否为空,避免出现异常。
- 内存溢出:在使用Stream进行大数据处理时,需要注意内存溢出的问题。如果数据量太大,而内存不足,会导致内存溢出。因此,需要根据数据量和机器的内存情况合理选择数据分块大小和处理方式,避免出现内存溢出的问题。
- 并行流的正确使用:在使用并行流时,需要注意线程安全的问题。如果多个线程同时修改同一个数据,可能会导致数据不一致的问题。因此,在使用并行流时,需要确保数据不会被多个线程同时修改。
- 终止操作:在使用Stream时,需要注意终止操作。如果没有正确地终止Stream,可能会导致数据丢失或者出现其他异常。因此,在使用Stream时,需要确保每个操作都正确地终止。
- 链式操作的可读性:在使用链式操作时,需要注意可读性问题。如果链式操作太长或者操作太多,会导致代码难以理解和维护。因此,在使用链式操作时,需要合理控制操作的长度和数量,提高代码的可读性和可维护性。
- 类型转换问题:在使用Stream的map方法进行类型转换时,需要注意类型转换的正确性。例如,将一个字符串列表映射为对应的整数列表时,如果转换出错会导致类型转换异常。因此,在进行类型转换时,需要确保转换的正确性。
- 延迟加载问题:Stream的延迟加载问题是指在Stream操作完成之后,结果不会立即计算并返回,而是需要在使用结果时才会进行计算。这种延迟加载的特性可以减少内存占用和提高性能,但也可能导致一些问题。因此,在使用Stream时需要注意,需要通过正确地终止操作、缓存结果、使用并行流和使用正确的操作等方法来解决该问题。
Stream 的应用场景
Java Stream的应用场景非常广泛,如果在实际的业务中以下类似的技术需求,那么都是可以使用Stream的相关能力助力业务实现:
- 数据筛选与过滤:Stream提供了filter方法,可以根据指定的条件筛选出符合要求的元素。在处理大量数据时,使用Stream可以避免手动编写循环和if语句的繁琐工作,使代码更加简洁易读。
- 映射操作:Stream的map方法可以将元素映射为其他对象。例如,可以将一个字符串列表映射为对应的字符串长度列表。
- 排序操作:Stream的sorted方法可以对元素进行排序。例如,可以将一个整数列表按照升序或降序排列。
- 聚合操作:Stream的reduce方法可以将元素进行聚合操作,例如将一个整数列表累加成一个整数。
- 收集操作:Stream的collect方法可以将元素收集到一个集合中。例如,可以将一个字符串列表收集到一个List中。
- 遍历操作:Stream的forEach方法可以遍历元素并执行指定的操作。例如,可以遍历一个整数列表并输出每个整数的值。
总结
总之,Java Stream可以应用于各种数据处理的场景,包括但不限于数据筛选与过滤、映射操作、排序操作、聚合操作、收集操作和遍历操作等。使用Stream可以简化代码、提高可读性和可维护性,并提高系统的性能和效率。
下一篇:深入了解Java 8 新特性:Stream流的实践应用(一)