Java实现List单字段分组,多字段求和

该文章已生成可运行项目,
public static void main(String[] args) {
        List<CarAreaStaticVo> carList = new ArrayList<>();
        carList.add(new CarAreaStaticVo(new BigDecimal("12"),30,"杭州","001","西湖","200"));
        carList.add(new CarAreaStaticVo(new BigDecimal("13"),10,"杭州","001","西湖","200"));
        carList.add(new CarAreaStaticVo(new BigDecimal("20"),10,"杭州","002","西湖","200"));
        carList.add(new CarAreaStaticVo(new BigDecimal("20"),20,"杭州","002","西湖","200"));
        carList.add(new CarAreaStaticVo(new BigDecimal("300"),20,"杭州","003","西湖","200"));
        // 以 code 为分组字段,对 achievement score money 分别求和
        List<CarAreaStaticVo> list = carList
                .stream()
                // groupingBy的第1个参数为分组字段,第2个参数为求和公式
                .collect(Collectors.groupingBy(CarAreaStaticVo::getDistrictCode, Collectors.reducing((sum, s) ->
                        new CarAreaStaticVo(sum.getHxPrice().add(s.getHxPrice()),sum.getHxCount() + s.getHxCount(),s.getDistrictName(),s.getDistrictCode(),s.getCityCode(),s.getCityName()))))
                .entrySet()
                .stream()
                .map(c -> c.getValue().get())
                .collect(Collectors.toList());
        for (CarAreaStaticVo item : list) {
            System.out.println(item);
        }

    }

CarAreaStaticVo(hxPrice=25, hxCount=40, districtName=杭州, districtCode=001, cityCode=西湖, cityName=200)
CarAreaStaticVo(hxPrice=40, hxCount=30, districtName=杭州, districtCode=002, cityCode=西湖, cityName=200)
CarAreaStaticVo(hxPrice=300, hxCount=20, districtName=杭州, districtCode=003, cityCode=西湖, cityName=200)

本文章已经生成可运行项目
<think>我们被要求使用JavaStreamAPI对多个字段进行分组求和。根据引用[1]和[2],我们看到单字段分组求和的示例,但用户需要多字段分组多字段分组可以通过在groupingBy中使用多个字段作为键来实现。常见的方法有:1.使用一个包含多个字段的类(如自定义类或Tuple)作为分组键。2.使用groupingBy的多参数形式,其中第一个参数是多个字段组合成的键,第二个参数是下游收集器(如summingInt,summingDouble等)。然而,在用户提供的引用中,引用[3]提到了对BigDecimal属性求和,这很有参考价值。具体到多字段分组,我们可以这样:例如,我们有一个对象列表,每个对象有字段A、B和数值字段C(需要求和)。我们想按A和B分组,然后对C求和。步骤:1.在groupingBy中,我们可以通过提供一个映射函数来生成一个复合键(例如,使用字符串连接、数组、List或自定义类)。但更推荐使用自定义类或Java中的现有组合类(如Pair或自己写一个内部类)来保持类型安全。2.然后使用一个下游收集器来求和,例如:Collectors.summingInt,summingLong,summingDouble,或者对于BigDecimal,我们可以使用reducing。由于引用[3]提到BigDecimal,我们将展示一个对BigDecimal求和的例子。示例代码:假设我们有一个类Item:classItem{privateStringfield1;privateStringfield2;privateBigDecimalvalue;//构造方法、getter和setter省略}我们想按field1和field2分组,然后对value求和。方法1:使用自定义类作为键(推荐,类型安全)我们可以创建一个类(比如Key)来包含field1和field2,并重写equals和hashCode方法。但也可以临时用List或String作为键。方法2:使用List作为键(简单但不那么直观):Map<List<String>,BigDecimal>result=list.stream().collect(Collectors.groupingBy(item->Arrays.asList(item.getField1(),item.getField2()),Collectors.reducing(BigDecimal.ZERO,Item::getValue,BigDecimal::add)));但是,使用List作为键可能会因为顺序问题(比如两个字段顺序颠倒)导致不同的键。而且,如果字段类型不同,则不好用List。方法3:使用字符串连接(简单,但可能产生冲突,比如"a","b"和"ab",""连接后都是"a|b"):Map<String,BigDecimal>result=list.stream().collect(Collectors.groupingBy(item->item.getField1()+"|"+item.getField2(),Collectors.reducing(BigDecimal.ZERO,Item::getValue,BigDecimal::add)));方法4(推荐):使用自定义类作为分组键。我们可以使用Java14的record(如果使用Java14及以上)或者自己写一个静态内部类。这里我们使用record(Java14及以上):recordKey(Stringfield1,Stringfield2){}然后:Map<Key,BigDecimal>result=list.stream().collect(Collectors.groupingBy(item->newKey(item.getField1(),item.getField2()),Collectors.reducing(BigDecimal.ZERO,Item::getValue,BigDecimal::add)));如果使用较低版本的Java,可以写一个内部类并重写equals和hashCode。另外,引用[2]中使用了Map<String,Object>的列表,所以我们也可以使用类似的方法处理Map元素的多字段分组求和。示例:对于List<Map<String,Object>>,按多个字段分组(比如按departmentName和employeeName)求和(比如对amount求和,假设amount是BigDecimal):Map<List<Object>,BigDecimal>result=data.stream().collect(Collectors.groupingBy(d->Arrays.asList(d.get("departmentName"),d.get("employeeName")),Collectors.reducing(BigDecimal.ZERO,d->(BigDecimal)d.get("amount"),BigDecimal::add)));但这里要注意类型转换,并且List<Object>作为键同样存在顺序和类型问题。因此,我们更推荐使用自定义类(或record)作为键。下面给出一个完整的示例代码(使用自定义类作为键,兼容Java8及以上):假设我们有一个交易记录类Transaction:publicclassTransaction{privateStringcategory;privateStringsubCategory;privateBigDecimalamount;//构造方法、getter和setter}我们想按category和subCategory分组,然后对amount求和。步骤:1.定义一个分组键类(内部静态类):publicstaticclassGroupKey{privatefinalStringcategory;privatefinalStringsubCategory;publicGroupKey(Stringcategory,StringsubCategory){this.category=category;this.subCategory=subCategory;}//重写equals和hashCode@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;GroupKeygroupKey=(GroupKey)o;returnObjects.equals(category,groupKey.category)&&Objects.equals(subCategory,groupKey.subCategory);}@OverridepublicinthashCode(){returnObjects.hash(category,subCategory);}}2.使用StreamAPI:List<Transaction>transactions=...;//数据源Map<GroupKey,BigDecimal>result=transactions.stream().collect(Collectors.groupingBy(t->newGroupKey(t.getCategory(),t.getSubCategory()),Collectors.reducing(BigDecimal.ZERO,Transaction::getAmount,BigDecimal::add)));注意:这里使用了Collectors.reducing来对BigDecimal求和。也可以使用Collectors.mapping和Collectors.reducing的组合,但上述方式更简洁。另外,如果使用Java12及以上,可以使用Collectors.summingBigDecimal(但标准库中没有这个,所以还是用reducing)。所以,我们使用Collectors.reducing(BigDecimal.ZERO,mapper,BinaryOperator)是通用的。但是,引用[3]中提到,可以使用Collectors.toMap或者Collectors.groupingBy结合Collectors.mapping和Collectors.reducing。这里我们使用groupingBy和reducing的组合。另外,还有一种写法是使用三个参数的groupingBy:第一个参数是分类函数,第二个参数是Map工厂,第三个参数是下游收集器。但我们这里不需要指定Map工厂,所以用两个参数的。总结:多字段分组求和的关键在于构建复合键,然后使用适当的求和收集器(对于BigDecimal,使用reducing;对于基本类型,可以使用summingInt等)。因此,我们给用户提供一个使用自定义键类进行多字段分组求和的示例代码。注意:如果用户的数据是Map组成的列表(如引用[2]),那么也可以使用类似的方法,只是键的构建方式不同。下面给出两个示例:一个是使用自定义对象列表,另一个是使用Map列表。</think>### Java Stream API 多字段分组求和示例 在 Java Stream API 中实现多字段分组求和,可以通过以下两种常用方法实现: #### 方法 1:使用 `Collectors.groupingBy` + `Collectors.summingXXX` ```java import java.util.*; import java.util.stream.Collectors; // 数据对象示例 class Order { private String category; private String region; private double amount; public Order(String category, String region, double amount) { this.category = category; this.region = region; this.amount = amount; } // Getters public String getCategory() { return category; } public String getRegion() { return region; } public double getAmount() { return amount; } } public class GroupingExample { public static void main(String[] args) { List<Order> orders = Arrays.asList( new Order("Electronics", "North", 1500.0), new Order("Clothing", "South", 800.0), new Order("Electronics", "North", 1200.0), new Order("Clothing", "South", 400.0) ); // 多字段分组求和 Map<List<String>, Double> result = orders.stream() .collect(Collectors.groupingBy( order -> Arrays.asList(order.getCategory(), order.getRegion()), Collectors.summingDouble(Order::getAmount) )); // 打印结果 result.forEach((keys, sum) -> System.out.println(keys.get(0) + " | " + keys.get(1) + " : " + sum)); } } ``` **输出**: ``` Electronics | North : 2700.0 Clothing | South : 1200.0 ``` #### 方法 2:使用 `Collectors.toMap`(更简洁) ```java Map<String, Double> result = orders.stream() .collect(Collectors.toMap( order -> order.getCategory() + "-" + order.getRegion(), // 组合键 Order::getAmount, Double::sum // 求和函数 )); // 打印结果 result.forEach((key, sum) -> System.out.println(key + " : " + sum)); ``` **输出**: ``` Electronics-North : 2700.0 Clothing-South : 1200.0 ``` #### 处理 BigDecimal 类型求和 ```java import java.math.BigDecimal; class Product { private String type; private String color; private BigDecimal price; // 构造方法和getters } Map<List<String>, BigDecimal> result = products.stream() .collect(Collectors.groupingBy( p -> Arrays.asList(p.getType(), p.getColor()), Collectors.reducing( BigDecimal.ZERO, Product::getPrice, BigDecimal::add ) )); ``` ### 关键说明: 1. **复合键构建**: - 使用 `Arrays.asList(field1, field2,...)` 创建复合分组键 - 或通过字符串拼接(如 `field1 + "-" + field2`) 2. **数值求和**: - 基本类型:`Collectors.summingDouble()`/`summingInt()` - BigDecimal:`Collectors.reducing(BigDecimal.ZERO, ..., BigDecimal::add)` 3. **类型安全**: - 对于复杂场景,建议创建专门的 `Key` 记录类: ```java record GroupKey(String category, String region) {} Map<GroupKey, Double> result = orders.stream() .collect(Collectors.groupingBy( o -> new GroupKey(o.getCategory(), o.getRegion()), Collectors.summingDouble(Order::getAmount) )); ``` ### 注意事项: 1. 复合键对象必须正确实现 `equals()` 和 `hashCode()` 方法 2. 字符串拼接方式需确保分隔符不会出现在字段值中 3. 对空值字段的处理建议使用 `Objects.toString(field, "DEFAULT")`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hexu_blog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值