5.Stream流的终结方法中的收集方法collect超详解

一.收集方法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)。


五.总结:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值