Java8 Stream的介绍

 2023-01-30
原文作者:蒋先森 原文地址:https://jlj98.top/

声明:本文使用JDK1.8

Java8提供了丰富的lambda的API。今天我们来讲讲关于其中的stream(流的操作)。对于List集合,有了stream的流操作,感觉如虎添翼。

生成一个List

    String str = "1,2,3,4,10,11,9,66,222,12";
    List<Integer> list = Stream.of(str.split(","))
                    .map(Integer::valueOf)
                    .filter(x-> !Objects.equals(x,3))
                    .sorted(Comparator.reverseOrder())
                    .limit(4)
                    .collect(Collectors.toList());

上面的代码Stream.of 为我们生成了一个List ,但是我们需要的Integer,所以我们还需要使用 map 来转换类型。然后对于生成的列表,我们不想要其中的3,于是我们使用 filter 来过滤掉列表中的3。对于现在列表我们又想是倒序后的列表的前四条数据,于是我们在使用 sorted 来对列表来进行倒序后,再使用 limit 取前四条。顺便说一句,对于比较,推荐使用Objects.equals(a,b).
下面来简单的介绍下stream。

map

map:对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素
map方法是对列表里面的对象的转换,比如上面的map的功能是吧String转换成Integer。除了这样的转换,你也可以对对象进行你需要的操作。比如现在有一个对象ListDTO:

    @Data
    public class ListDTO{
        public Integer id;
        public String name;
        public ListDTO() {
        }
        public ListDTO(Integer id, String name) {
            this.id = id;
            this.name = name;
        }
    }

现在这么一个List ,你现在只想得到这个列表中ListDTO对象里面的id,现在你就可以这样操作:

    List<Integer> idList = beans.stream().map(ListDTO::getId).collect(Collectors.toList());

如果你想要得到name的列表,也可以进行上面类似的操作。下面对List列表的元素先转换成大写,在用逗号把元素拼接起来。

    // 将字符串换成大写并用逗号拼接起来
    List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
    String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
    System.out.println(G7Countries);

输出:

    USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

flatMap

类似于map,但是flatMap是对Stream之间的转换。

    String[][] data = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};
    Stream<String[]> temp = Arrays.stream(data);
    Stream<String[]> stream = temp.filter(x -> "a".equals(x.toString()));

count

count()是对列表的聚合,统计列表的数量。

    Long count = list.stream().count();

filter

对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素

distinct

distinct是对stream里面的元素进行去重,有点类似于SQL里面的distinct去重

limit

对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素

stream 生成 map

上面我们介绍了stream里面的toList(),下面我们来介绍下如何使用stream直接生成map,而不是像以前一样,需要循环后再一个一个的put进map。虽然我们也可以第三方的jar生成Map,比如Google的某些jar,使用里面的Maps.uniqueIndex()也可以直接把List转换成Map。

toMap

利用Collectors里面的toMap对List列表转换Map。

    // 这种toMap,当出现Key相同时,会出现 Duplicate key 问题
    Map<Integer,String> beanMap = beans.stream()
                    .collect(Collectors.toMap(x->x.getId(), m->m.getName()));
    
    //解决key冲突问题,使用新的value替换原来的value
    Map<Integer, String> map = beans.stream()
        .collect(Collectors.toMap(SimpleDTO::getId, SimpleDTO::getName, (v1, v2) -> v2));

前者的toMap使用的是 throwingMerger,在出现key相同时,抛出异常;后者的toMap使用的是mergeFunction,如示例中,如果出现key相同的元素,新的key能覆盖旧的。

groupingBy

利用Collectors里面的groupingBy,可以对列表进行分组,总和和排序。

    public static void main(String[] args) {
    
        List<String> items = Arrays.asList("Apple", "Apple", "orange",
                "Apple", "orange", "banana", "papaya");
    
        Map<String, Long> result = items
                .stream()
                .collect(Collectors
                        .groupingBy(Function.identity(), Collectors.counting()));
        System.out.println(result);
    
        //排序
        Map<String, Long> finalMap = new LinkedHashMap<>();
        //sort a map and add to finalMap
        result.entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue()
                        .reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));
    
        // 根据value 倒叙
        Map<String, SimpleDTO> sampleMap = BeanData.getDataList()
                .stream().collect(Collectors.toMap(SimpleDTO::getName, m->m));
        Map<String, SimpleDTO> newMap = sampleMap.entrySet().stream()
                .sorted(Map.Entry.comparingByValue((o1, o2) -> {
                    if (o1.getAge() > o2.getAge()) {
                        return -1;
                    }
                    if (o1.getAge() < o2.getAge()) {
                        return 1;
                    }
                    return 0;
                })).collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (o1, o2) -> o1,
                        LinkedHashMap::new));
        System.out.println(newMap);
        // map 根据key正序排列
        Map<String, SimpleDTO> keyMap = sampleMap.entrySet().stream()
                .sorted(Map.Entry.comparingByKey()).collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (o1, o2) -> o1,
                        LinkedHashMap::new));
        System.out.println(keyMap);
    
        System.out.println(finalMap);
        //数据统计
        List<SimpleDTO> beans = BeanData.getBeanDataList();
        Map<Integer, Long> countMap = beans
                .stream().collect(
                        Collectors.groupingBy(SimpleDTO::getId, Collectors.counting()));
        System.out.println(countMap);
    
        Map<Integer, List<SimpleDTO>> listMap = beans.stream().collect(
                Collectors.groupingBy(SimpleDTO::getId));
        System.out.println(JSON.toJSON(listMap));
    
        //Map 的value值自行聚合
        Map<Integer, Set<String>> setMap = beans.stream().collect(
                Collectors.groupingBy(SimpleDTO::getId, Collectors.mapping(SimpleDTO::getName, Collectors.toSet()))
        );
        System.out.println(JSON.toJSON(setMap));
    
        //Map的value重新聚合一个新的对象
        Map<Integer, List<UserDTO>> collectMap = beans.stream()
                .collect(Collectors.groupingBy(SimpleDTO::getId,
                        Collectors.collectingAndThen(Collectors.toList(), input -> input.stream()
                                .map(m -> {
                                    UserDTO user = new UserDTO();
                                    user.setId(m.getId());
                                    user.setUsername(m.getName());
                                    return user;
                                }).collect(Collectors.toList())
                        )));
        System.out.println(JSON.toJSON(collectMap));
    
        SimpleDTO dto1 = new SimpleDTO(1, "Jack", "1");
        SimpleDTO dto2 = new SimpleDTO(2, "James", "2");
        SimpleDTO dto3 = new SimpleDTO(3, "Hangzhou", "3");
        SimpleDTO dto4 = new SimpleDTO(3, "Hangzhou", "4");
        List<SimpleDTO> list = new ArrayList<>();
        list.add(dto1);
        list.add(dto2);
        list.add(dto3);
        list.add(dto4);
    
        //自行拼接KEY
        Map<String, List<SimpleDTO>> mapKey = list.stream()
                .collect(Collectors.groupingBy(e -> fetchGroupKey(e)));
        System.out.println(mapKey);
    
    }
    
    private static String fetchGroupKey(SimpleDTO dto) {
        return dto.getId() + "_" + dto.getName();
    }

总和

    List<String> items = Arrays.asList("Apple", "Apple", "orange",
                            "Apple", "orange", "banana", "papaya");
    Map<String,Long> result = items.stream()
                                    .collect(Collectors
                                    .groupingBy(Function.identity(),Collectors.counting()));

输出结果:

    {papaya=1, banana=1, orange=2, Apple=3}

排序

    Map<String, Long> finalMap = new LinkedHashMap<>();
            //Sort a map and add to finalMap
            result.entrySet().stream()
                    .sorted(Map.Entry.<String, Long>comparingByValue()
                            .reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));
    System.out.println(finalMap);

分组

现在有数据

    public static List<ListDTO> getBeanDataList(){
          List<ListDTO> listDTOS = Arrays.asList(
          new ListDTO(1,"孙博"), new ListDTO(1,"二代"),new ListDTO(1,"孙博"),
          new ListDTO(2,"戴硕"),new ListDTO(2,"戴硕"),new ListDTO(2,"赛克"),
          new ListDTO(3,"二代"),new ListDTO(3,"路痴"),new ListDTO(3,"路痴"),
          new ListDTO(4,"赛克"),new ListDTO(4,"二代"),new ListDTO(4,"路痴")
       );

下面更加其中的ID作为Map的key,对上面的List进行Map.

    Map<Integer,List<ListDTO>> listMap = beans.stream().collect(
                    Collectors.groupingBy(ListDTO::getId));

把对象中的name取出并塞到一个Set中

    Map<Integer,Set<String>> setMap = beans.stream().collect(
    Collectors.groupingBy(ListDTO::getId,Collectors.mapping(ListDTO::getName,Collectors.toSet()))
    );

其他

现在需要对上面的集合,根据ID,对数据进行统计。

    Map<Integer,Long> countMap = beans
                    .stream().collect(
              Collectors.groupingBy(ListDTO::getId,Collectors.counting())
    );

Stream 和 parallelStream

Stream 是普通的对流操作,而 parallelStream 是对集合进行并发操作,看下面的对两者的简单比较:

    public static void main(String[] args) {
        List<SimpleDTO> list = new ArrayList<>();
        for (int i = 1; i < 100000; i++) {
            SimpleDTO dto = new SimpleDTO();
            dto.setId(i);
            dto.setName("测试员" + i + "号");
            dto.setContent("Stream 测试普通和并行的效率");
            list.add(dto);
        }
    
        List<SimpleDTO> testList = list;
    
        Long stattTime = System.currentTimeMillis();
        List<String> list1 = list.stream()
                .filter(x -> x.getId() > 1000)
                .map(SimpleDTO::getName)
                .collect(Collectors.toList());
        Long endTime = System.currentTimeMillis();
    
        System.out.println("stream 耗时:" + (endTime - stattTime) + "ms");
    
        List<String> list2 = testList.parallelStream()
                .filter(x -> x.getId() > 1000)
                .map(SimpleDTO::getName)
                .collect(Collectors.toList());
        Long endTime1 = System.currentTimeMillis();
    
        System.out.println("parallelStream 耗时:" + (endTime1 - endTime) + "ms");
    }

结果:

    stream 耗时:77ms
    parallelStream 耗时:19ms

从最终的耗时可以看出,对于 parallelStream 处理大量数据的时候,效率还是有很大的提升的。