Java8流的Collectors知识点补充

0.绪论

我们可以将流看做花哨又懒惰的数据集迭代器,它们支持两种类型的操作:
1.中间操作如(filter、map)
2.终端操作如(count、findFirst、foreach和reduce)
中间操作可以链接起来讲一个流转化为另一个流,这些操作并不会消耗流,其目的就是建立一个流水线,而终端操作会消耗流以产生一个最终的结果,例如返回流中的一个最大元素。

1.filter筛选

Streams接口支持filter方法,该方法支持一个谓词作为参数,并返回一个包括所有符合谓词的元素的流。

List<Dish> vagetarianMenu = menu
								.stream()
								.filter(Dish::isVegetarian)
								.collect(Collectors.toList);

具体的效果见下图
在这里插入图片描述


2.Collectors reducing收集器

 import java.util.stream.Collectors;
 Employee employee = new Employee(9000, "man", 19, "888888888");
 Employee employee1 = new Employee(8000, "sex", 21, "77777777777777777");
 Employee employee2 = new Employee(29000, "man", 22, "888888888888888777");

 int salarySum = Arrays.asList(employee, employee1, employee2)
                .stream()
                .collect(Collectors.reducing(0,
                        Employee::getSalary, Integer::sum));
 System.out.println(salarySum); //46000

3.Collectors groupingBy多级分组

  List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));

Map<Type, Map<CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel = menu
                .stream()
                .collect(
                        Collectors.groupingBy(Dish::getType,
                                Collectors.groupingBy(dish -> {
                                    if (dish.getCalories() <= 400) {
                                        return CaloricLevel.DIET;
                                    } else if (dish.getCalories() <= 700) {
                                        return CaloricLevel.NORMAL;
                                    } else {
                                        return CaloricLevel.FAT;
                                    }
                                })));
System.out.println(dishesByTypeCaloricLevel);
/**
{
OTHER={NORMAL=[french fries, pizza], DIET=[rice, season fruit]}, 
FISH={NORMAL=[salmon], DIET=[prawns]}, 
MEAT={FAT=[pork], NORMAL=[beef], DIET=[chicken]}
}
*/
public enum CaloricLevel {
    DIET, NORMAL, FAT
}
public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;

    public enum Type {MEAT, FISH, OTHER}

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }

    @Override
    public String toString() {
        return name;
    }
}

在这里插入图片描述

4.Collectors 按子组收集数据

 List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));

Map<Dish.Type,Long>  typesCount= menu
									.stream()
									.collect(Collectors.groupingBy(Dish::getType,Collectors.counting()));
System.out.println(typesCount); // {MEAT=3, FISH=2, OTHER=4}

5.把收集器的结果转换为另外一种类型

首先举一个例子:分类后拿到热量最高的菜

List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));

Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu
                .stream()
                .collect(Collectors.groupingBy(Dish::getType,
                                Collectors.maxBy(Comparator.comparingInt(Dish::getCalories))));
System.out.println(mostCaloricByType);
// result:{OTHER=Optional[pizza], FISH=Optional[salmon], MEAT=Optional[pork]}

但是这个Map中的value是Optional,我们想直接拿到Dish,这时候我们可以使用collectingAndThen,如下

List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));
 Map<Type, Dish> mostCaloricByType = menu
                .stream()
                .collect(Collectors.groupingBy(
                        Dish::getType,
                        Collectors.collectingAndThen(
                                Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
                                Optional::get)));
System.out.println(mostCaloricByType);

这个工厂方法接受两个参数——要转换的收集器以及转换函数,并返回另一个收集器。这个收集器相当于旧收集器的一个包装, collect操作的最后一步就是将返回值用转换函数做一个映射。在这里,被包起来的收集器就是用maxBy建立的那个,而转换函数Optional::get则把返回的Optional中的值提取出来。前面已经说过,这个操作放在这里是安全的,因为reducing收集器永远都不会返回Optional.empty()。其结果是下面的Map:
{FISH=salmon, OTHER=pizza, MEAT=pork}


6.与groupingBy联合使用的其他收集器的例子

例子1:

 List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));

 Map<Dish.Type, Integer> totalCaloriesByType =
                menu
                    .stream()
                    .collect(Collectors.groupingBy(Dish::getType,
                                Collectors.summingInt(Dish::getCalories)));
System.out.println(totalCaloriesByType);
// {MEAT=1900, OTHER=1550, FISH=750}

例子2:

List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));

        Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
                menu
                        .stream()
                        .collect(Collectors.groupingBy(Dish::getType, Collectors.mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) {
                                        return CaloricLevel.DIET;
                                    } else if (dish.getCalories() <= 700) {
                                        return CaloricLevel.NORMAL;
                                    } else {
                                        return CaloricLevel.FAT;
                                    }
                                },
                                Collectors.toSet())));
System.out.println(caloricLevelsByType);
// {MEAT=[NORMAL, FAT, DIET], OTHER=[NORMAL, DIET], FISH=[NORMAL, DIET]}

也可以这样:

 Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType =
                menu
                        .stream()
                        .collect(Collectors.groupingBy(Dish::getType, Collectors.mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) {
                                        return CaloricLevel.DIET;
                                    } else if (dish.getCalories() <= 700) {
                                        return CaloricLevel.NORMAL;
                                    } else {
                                        return CaloricLevel.FAT;
                                    }
                                },
                                Collectors.toCollection(HashSet::new))));
System.out.println(caloricLevelsByType);

7.分区

分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以分为两组——true是一组, false是一组

  List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH));
  Map<Boolean, List<Dish>> partitionedMenu =
                menu
                    .stream()
                    .collect(Collectors.partitioningBy(Dish::isVegetarian));
 System.out.println(partitionedMenu);
 // {false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}

细分(素食菜肴(按类型)):

 Map<Boolean, Map<Type, List<Dish>>> vegetarianDishesByType =
                menu
                    .stream()
                    .collect(Collectors.partitioningBy(Dish::isVegetarian,
                            Collectors.groupingBy(Dish::getType)));
        System.out.println(vegetarianDishesByType);
// {false={MEAT=[pork, beef, chicken], FISH=[prawns, salmon]}, true={OTHER=[french fries, rice, season fruit, pizza]}}        

附录

1.Employee类

public class Employee {

    private Integer salary;
    private String sex;
    private Integer age;
    private String phone;

    public Employee(Integer salary, String sex, Integer age, String phone) {
        this.salary = salary;
        this.sex = sex;
        this.age = age;
        this.phone = phone;
    }

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "salary=" + salary +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
}

2.其他人写的很不错关于流操作的文章
3.Collectors类的静态工厂方法
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java 8中,使用StreamCollectors.groupingBy方法可以对数据进行分组操作。但是需要注意的是,如果分组的字段数据存在丢失的情况,可能是由于以下原因导致的: 1. 分组字段的数据类型不一致:如果分组字段的数据类型不一致,可能会导致分组时数据丢失。例如,如果分组字段是一个对象的属性,而该属性的数据类型在不同的对象中不一致,那么在分组时可能会导致某些数据被丢弃。 2. 分组字段的hashCode和equals方法未正确重写:在进行分组操作时,需要使用分组字段的hashCode和equals方法来确定分组的依据。如果这两个方法未正确重写,可能会导致分组时数据丢失。 为了避免数据丢失的情况发生,可以采取以下措施: 1. 确保分组字段的数据类型一致:在进行分组操作之前,可以先对分组字段的数据类型进行统一,保它们具有相同的数据类型。 2. 重写分组字段的hashCode和equals方法:如果分组字段是一个自定义对象的属性,需要确保该属性的hashCode和equals方法已正确重写,以确保分组操作的准确性。 下面是一个示例代码,演示了如何使用Java 8的StreamCollectors.groupingBy方法进行分组操作: ```java import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class GroupingByDemo { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eva"); // 按照名字的首字母进行分组 Map<Character, List<String>> groups = names.stream() .collect(Collectors.groupingBy(name -> name.charAt(0))); // 输出分组结果 groups.forEach((key, value) -> System.out.println(key + ": " + value)); } } ``` 运行以上代码,将会按照名字的首字母进行分组,并输出分组结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boy快快长大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值