一.收集方法collect方法概述:
-
收集方法collect可以收集流中的数据,大多放到单列集合和双列集合中(单列集合包括List集合、Set集合,双列集合包括Map集合)。
-
注:双列集合Map的键不能重复,值可以重复,因此使用collect方法将数据收集到Map集合中一定要保证键不重复,如果键重复了打印Map集合等操作会报错
二.stream流里的工具类Collectors:
1.工具类Collectors里的静态方法toList:该方法用于把流里的数据全部收集到单列集合List中
stream流里有一个工具类Collectors,工具类Collectors里有一个静态方法toList,toList方法的底层会帮我们创建一个ArrayList集合,然后把stream流里面的所有数据都收集到单列集合List中(ArrayList集合属于单列集合List的实现类),toList方法常和stream流里的collect方法结合使用:
2.工具类Collectors里的静态方法toSet:该方法用于把流里的数据全部收集到单列集合Set中
stream流里有一个工具类Collectors,工具类Collectors里有一个静态方法toSet,toSet方法的底层会帮我们创建一个HashSet集合,然后把stream流里面的所有数据都收集到单列集合Set中(HashSet集合属于单列集合Set的实现类),toSet方法常和stream流里的collect方法结合使用:
3.注意事项:流里的数据分别收集到List集合中和Set集合中的区别
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Test2 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list=new ArrayList<>();
//2.添加元素
Collections.addAll(list,"张无忌-男-15","张无忌-男-15","周芷若-女-14","赵敏-女-13","张强-男-20",
"张三丰-男-100","张翠山-男-40","张良-男-35","王二麻子-男-37","谢广坤-男-41");
/*list集合中的数据的格式为姓名-性别-年龄*/
//3.过滤元素并收集
List<String> newList = list.stream()
.filter(s -> "男".equals(s.split("-")[1])) //过滤不符合要求的数据,s此时代表stream流中的每一个数据即list集合中的每一个元素
.collect(Collectors.toList());//收集数据
//4.打印newList集合
System.out.println(newList);
//运行结果为[张无忌-男-15, 张无忌-男-15, 张强-男-20, 张三丰-男-100, 张翠山-男-40, 张良-男-35, 王二麻子-男-37, 谢广坤-男-41]
}
}
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
public class Test3 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加元素
Collections.addAll(list, "张无忌-男-15", "张无忌-男-15","周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
/*list集合中的数据的格式为姓名-性别-年龄*/
//3.过滤元素并收集
Set<String> newList = list.stream()
.filter(s -> "男".equals(s.split("-")[1])) //s此时代表stream流中的每一个数据即list集合中的每一个元素
.collect(Collectors.toSet());
//4.打印newList集合
System.out.println(newList);
//运行结果为[张强-男-20, 张良-男-35, 张三丰-男-100, 张无忌-男-15, 谢广坤-男-41, 张翠山-男-40, 王二麻子-男-37]
}
}
通过上述两段代码可以看出,分别收集到单列集合List中和单列集合Set中,最终打印的结果会有所不同
->收集到List集合中,会保证元素的顺序不变,而且元素可以重复(因为List集合添加元素的特点是有序、可重复、有索引)
->收集到Set集合中,元素的顺序可能会被打乱,而且元素不能重复即系统会自动去重(因为Set集合添加元素的特点是无序、不重复、无索引)
4.工具类Collectors里的静态方法toMap:该方法用于把流里的数据全部收集到双列集合Map中
stream流里有一个工具类Collectors,工具类Collectors里有一个静态方法toMap且只有两个形参,该toMap方法的底层会帮我们创建一个HashMap集合,然后把stream流里面的所有数据都收集到双列集合Map中(HashMap集合属于双列集合Map的实现类),该toMap方法常和stream流里的collect方法结合使用:
对于只有两个形参的toMap方法,该toMap方法的两个形参都为函数式接口Function,该函数式接口Function有两个泛型,第一个泛型表示流里面的数据的类型,第二个泛型是要收集到Map集合中的数据的类型;
对于toMap方法的两个形参:
第一个形参Function接口用于指定键的生成规则:
-
该Function接口有两个泛型,第一个泛型表示流中每一个数据的类型;第二个泛型表示生成的Map集合中键的数据类型
-
创建函数式接口Function类后需要重写里面的抽象方法apply,方法apply的形参依次表示流里面的每一个数据,apply方法的方法体就是生成键的代码(这个键是最终的Map集合里的),apply方法的返回值就是返回已经生成的键
-
注:Function接口的第一个泛型类型和apply方法的形参类型对应,Function接口的第二个泛型类型和apply方法的返回值类型对应
第二个形参Function接口用于指定值的生成规则:
-
该Function接口有两个泛型,第一个泛型表示流中每一个数据的类型;第二个泛型表示生成的Map集合中值的数据类型
-
创建函数式接口Function类后需要重写里面的抽象方法apply,方法apply的形参依次表示流里面的每一个数据,apply方法的方法体就是生成值的代码(这个值是最终的Map集合里的),apply方法的返回值就是返回已经生成的值
-
注:Function接口的第一个泛型类型和apply方法的形参类型对应,Function接口的第二个泛型类型和apply方法的返回值类型对应
三.实例:
1.collect方法收集流中的数据,放到单列集合List中:
需求:
把所有的男性收集起来。
解法一:
package com.itheima.a01mystream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class StreamDemo10 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list=new ArrayList<>();
//2.添加元素
Collections.addAll(list,"张无忌-男-15","周芷若-女-14","赵敏-女-13","张强-男-20",
"张三丰-男-100","张翠山-男-40","张良-男-35","王二麻子-男-37","谢广坤-男-41");
/*list集合中的数据的格式为姓名-性别-年龄*/
//3.过滤元素并收集
List<String> newList = list.stream().filter(new Predicate<String>() { /*list.stream()获取stream流,调用filter方法用于过滤数据*/
@Override
public boolean test(String s) { //s此时代表stream流中的每一个数据即list集合中的每一个元素
String[] result = s.split("-");
/*使用-切割list集合中的每一个元素后,每一个元素会放入数组中,
此时数组的0索引为姓名,1索引为性别,2索引为年龄*/
if (result[1].equals("男")) return true; //返回true代表把result数组中1索引上是男性的数据留下,其余过滤掉
return false; //此时返回false,代表把result数组中1索引上不是男性的数据过滤掉
}
}).collect(Collectors.toList());
//4.打印newList集合
System.out.println(newList);
}
}
解法二:lambda表达式简化后
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class Test2 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list=new ArrayList<>();
//2.添加元素
Collections.addAll(list,"张无忌-男-15","周芷若-女-14","赵敏-女-13","张强-男-20",
"张三丰-男-100","张翠山-男-40","张良-男-35","王二麻子-男-37","谢广坤-男-41");
/*list集合中的数据的格式为姓名-性别-年龄*/
//3.过滤
/* "男".equals(s.split("-")[1])和
s.split("-")[1].equals("男")都可以,
但通常的习惯是使用确定的数据调用方法与不确定的数据进行操作,
所以"男".equals(s.split("-")[1])更好一些,
因为确定的数据不可能是null,不确定的数据可能是null,
如果不确定的数据为null,并且不确定的数据调用方法就会出现空指针异常,
而确定的数据不可能为null,确定的数据调用方法不会出现空指针异常 */
List<String> newList = list.stream()
.filter(s -> "男".equals(s.split("-")[1])) //过滤不符合要求的数据
.collect(Collectors.toList());//收集数据
/*Collectors是stream流里的工具类,
Collectors里有一个静态方法toList,
toList方法的底层会帮我们创建一个ArrayList集合,
然后把流里面的数据收集到该ArrayList集合中*/
//4.打印newList集合
System.out.println(newList);
}
}
2.collect方法收集流中的数据,放到单列集合Set中:
需求:
把所有的男性收集起来。
解法:
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
public class Test3 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加元素
Collections.addAll(list, "张无忌-男-15", "张无忌-男-15","周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
/*list集合中的数据的格式为姓名-性别-年龄*/
//3.过滤元素并收集
Set<String> newList = list.stream()
.filter(s -> "男".equals(s.split("-")[1])) //s此时代表stream流中的每一个数据即list集合中的每一个元素,虽然list集合属于ArrayList类,但list集合里的数据是可以收集到Set类中的,因为这里强调的是数据,和类无关
.collect(Collectors.toSet());
//4.打印newList集合
System.out.println(newList);
//运行结果为[张强-男-20, 张良-男-35, 张三丰-男-100, 张无忌-男-15, 谢广坤-男-41, 张翠山-男-40, 王二麻子-男-37]
}
}
3.collect方法收集流中的数据,放到双列集合Map中:
收集到Map集合中首先要规定谁作为键,谁作为值,因为Map集合是双列集合
需求:
把所有的男性收集起来,其中键:姓名(姓名是字符串型即String型),值:年龄(年龄是整型,要用泛型,所以可以用Integer型),性别就不需要存了,因为只有男性的数据会被收集起来,意味着收集起来的数据一定是男性,此时性别已知,就没必要再收集了
解法:
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Test4 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加元素
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
//3.过滤元素并收集
Map<String, Integer> map = list.stream().filter(s -> "男".equals(s.split("-")[1])) //意味着只把性别为男的留下
/*
* toMap方法:第一个形参表示键的生成规则
* 第二个形参表示值的生成规则
*
* toMap方法的第一个形参Function接口有两个泛型:
* 第一个泛型:表示流中每一个数据的类型
* 第二个泛型:表示生成的Map集合中键的数据类型
* 创建Function接口的类后就需要重写Function接口里的抽象方法apply:
* apply方法的形参:依次表示流里面的每一个数据
* apply方法的方法体:生成键的代码(这个键是最终的Map集合里的)
* apply方法的返回值:返回已经生成的键
*
* toMap方法的第二个形参Function接口有两个泛型:
* 第一个泛型:表示流中每一个数据的类型
* 第二个泛型:表示生成的Map集合中值的数据类型
* 创建Function接口的类后就需要重写Function接口里的抽象方法apply:
* apply方法的形参:依次表示流里面的每一个数据
* apply方法的方法体:生成值的代码(这个值是最终的Map集合里的)
* apply方法的返回值:返回已经生成的值
*
*
* */
.collect(Collectors.toMap(new Function<String, String>() {
/*toMap的第一个形参表示键的生成规则,此时键是姓名,姓名是字符串型,所以第一个形参Function接口的第二个泛型是String型*/
@Override
public String apply(String s) {
//s此时代表stream流中的每一个数据即集合中的每一个元素,如张无忌-男-15,被-切割后得到的数组中0索引是姓名,1索引是性别,2索引是年龄
String[] result = s.split("-");
//此时result的0索引表示姓名
String name = result[0];
return name;
}
},
new Function<String, Integer>() {
/*toMap的第二个形参表示值的生成规则,此时值是年龄,年龄是整型(整型是基本数据类型,但此时要泛型,就要用包装类),所以第二个形参Function接口的第二个泛型是Integer型;*/
@Override
public Integer apply(String s) {
//s此时代表stream流中的每一个数据即集合中的每一个元素,如张无忌-男-15,被-切割后得到的数组中0索引是姓名,1索引是性别,2索引是年龄
String[] result = s.split("-");
//此时result的2索引表示年龄,但是此时的年龄是字符串型,所以要转为整型
int age = Integer.parseInt(result[2]); //有自动拆箱的功能
return age;
}
}));
//4.打印map集合
System.out.println(map);
//运行结果为{张强=20, 张良=35, 张翠山=40, 王二麻子=37, 张三丰=100, 张无忌=15, 谢广坤=41}
}
}
使用lambda表达式简化后:
package com.itheima.a02test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
public class Test4 {
public static void main(String[] args) {
//1.创建集合
ArrayList<String> list = new ArrayList<>();
//2.添加元素
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
"张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
//3.过滤元素并收集
Map<String, Integer> map = list.stream()
.filter(s -> "男".equals(s.split("-")[1])) //意味着只把性别为男的留下
.collect(Collectors.toMap(
s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])));
//s此时代表stream流中的每一个数据即集合中的每一个元素,如张无忌-男-15,被-切割后得到的数组中0索引是姓名,1索引是性别,2索引是年龄
//4.打印map集合
System.out.println(map);
//运行结果为{张强=20, 张良=35, 张翠山=40, 王二麻子=37, 张三丰=100, 张无忌=15, 谢广坤=41}
}
}
四.注意事项:
核心:
双列集合Map的键不能重复,值可以重复,因此使用collect方法将数据收集到Map集合中一定要保证键不重复,如果键重复了打印Map集合等操作会报错,原因和只有两个形参的toMap方法有关
上述图片中第1438行代码的uniqKeysMapAccumulator方法的形参分别是键的规则和值的规则,uniqKeysMapAccumulator方法的方法体如下:
上述图片中第181行代码会调用putIfAbsent方法,putIfAbsent方法的形参依次是键和值,putIfAbsent方法的方法体如下:
上述图片中第837行的putIfAbsent方法的形参中K key是键, V value是值,在putIfAbsent方法的方法体中,(第838行代码)会先通过键找值(v是值),
如果值是null,表示当前的键是第一次出现,就会执行if语句把数据添加到Map集合中,再返回值;
如果值不是null,表示此时的键不是第一次出现,就不会执行if语句即不会添加数据到Map集合中,会直接返回值。
上述图片中第182行代码会对值即u进行判断,如果u即值不是null,就会抛出一个异常duplicateKeyException(k, u, v)。