实现效果:
List<Cat> cats = new ArrayList<>();
cats.add(new Cat("aaa", 1, 2));
cats.add(new Cat("bbb", 5, 6));
cats.add(new Cat("aaa", 3, 4));
cats.add(new Cat("bbb", 7, 8));
"aaa", 4, 6
"bbb", 12, 14
运行环境:jdk8
Map<String, Double> map1 = values.stream().collect(Collectors.groupingBy(EActual::getL2, Collectors.summingDouble(k -> Double.parseDouble(k.getSalaryValueE()))));
利用Stream流对集合中的字段进行汇总求和,目前提供的API仅支持单列求和,不满足日常使用,提供以下两种方式:
方式一:反射实现
/**
* 计算List中各列之和
* @param dataList 数据集合
* @param clazz 对象
* @param includeList 要计算的列
* @return
* @param <T>
* @throws Exception
*/
public static <T> T getSumByList(List<T> dataList, Class<T> clazz, List<String> includeList) throws Exception {
if (includeList == null) {
throw new RuntimeException("不能为null,至少为空集合");
}
Map<String, Object> map = new HashMap<>();
Field[] declaredFields = clazz.getDeclaredFields();
//遍历数据集合
for (T t : dataList) {
//获取对象的所有属性
for (Field field : declaredFields) {
try {
String name = field.getName();
if (!includeList.contains(name)) continue;
field.setAccessible(true);
Object o = field.get(t);
if (o == null || ObjectUtils.isEmpty(o)) continue;
//如果计算总值的map已经包含该字段则直接获取
if (map.containsKey(name)) {
if (o instanceof Double) {
Double o1 = (Double)map.get(name);
map.put(name, o1 + (Double) o);
} else if (o instanceof Integer) {
Integer o1 = (Integer)map.get(name);
map.put(name, o1 + (Integer) o);
} else {
log.info("属性类型不匹配,(支持Double,Integer):{}",field.getName());
}
} else {
if (o instanceof Double) {
map.put(name, (Double) o);
} else if (o instanceof Integer) {
map.put(name, (Integer) o);
} else {
log.info("属性类型不匹配,(支持Double,Integer):{}",field.getName());
}
}
} catch (Exception e) {
//出异常则跳过该属性
throw new RuntimeException("发生异常跳过属性",e);
}
}
}
return mapToObject(map, clazz);
}
/**
* map集合转对象,上面方法中专用
*
* @param map
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
private static <T> T mapToObject(Map<String, Object> map, Class<T> clazz) throws Exception {
if (map == null) {
return null;
}
T obj = clazz.getDeclaredConstructor().newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
Method setter = property.getWriteMethod();
if (setter != null) {
Object o = map.get(property.getName());
if (o != null) {
try {
setter.invoke(obj, o);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
}
}
}
}
return obj;
}
Map<String, List<CurrentStaffCostCpInfoDTO>> curGroupMap = currentStaffGroupList.stream().collect(Collectors.groupingBy((k->empTypeMap.get(k.getEmpType())+"#"+k.getL1()+"#"+k.getL2()+"#"+k.getMonth())));
// 获取要计算的列
List<String> curFieldNames = new ArrayList<>();
curFieldNames.add("monthPeopleNum");//人数
for (Field field : CurrentStaffCostCpInfoDTO.class.getDeclaredFields()) {
String name = field.getName();
if (4 == name.length() && name.startsWith("y")){
curFieldNames.add(name);
}
}
currentStaffGroupList.clear();
curGroupMap.forEach((k,v)->{
try {
String[] arr = k.split("#");
CurrentStaffCostCpInfoDTO costCpInfoSum = ReflectUtils.getSumByList(v, CurrentStaffCostCpInfoDTO.class, curFieldNames);
costCpInfoSum.setEmpType(arr[0]);
costCpInfoSum.setL1(arr[1]);
costCpInfoSum.setL2("null".equals(arr[2]) ? null : arr[2]);
costCpInfoSum.setMonth(Integer.valueOf(arr[3]));
currentStaffGroupList.add(costCpInfoSum);
} catch (Exception e) {
log.warn("现员计算List中各列之和异常",e);
}
});
方式二:利用Stream中的Collectors.toMap
/**
* 汇总求和Amount1-10
* @param baseReportAmountList
* @return
*/
private static Map<String, BudgetBaseReportDTO> getReportDTOMap(List<BudgetBaseReportDTO> baseReportAmountList) {
Map<String, BudgetBaseReportDTO> l2GroupAmountMap = baseReportAmountList.stream().collect(Collectors.toMap((k -> k.getL2() + "#" + k.getBaseLineName() + "#" + k.getEmpType() + "#" + k.getCostSubject()), v -> v,
(v1, v2) -> {
BudgetBaseReportDTO dto = new BudgetBaseReportDTO();
dto.setEmpType(v1.getEmpType());
dto.setL1(v1.getL1());
dto.setL2(v1.getL2());
dto.setBaseLineName(v1.getBaseLineName());
dto.setCostSubject(v1.getCostSubject());
dto.setCostSubjectDesc(v1.getCostSubjectDesc());
dto.setAmount1(ArithUtil.addTotal(v1.getAmount1(), v2.getAmount1()));
dto.setAmount2(ArithUtil.addTotal(v1.getAmount2(), v2.getAmount2()));
dto.setAmount3(ArithUtil.addTotal(v1.getAmount3(), v2.getAmount3()));
dto.setAmount4(ArithUtil.addTotal(v1.getAmount4(), v2.getAmount4()));
dto.setAmount5(ArithUtil.addTotal(v1.getAmount5(), v2.getAmount5()));
dto.setAmount6(ArithUtil.addTotal(v1.getAmount6(), v2.getAmount6()));
dto.setAmount7(ArithUtil.addTotal(v1.getAmount7(), v2.getAmount7()));
dto.setAmount8(ArithUtil.addTotal(v1.getAmount8(), v2.getAmount8()));
dto.setAmount9(ArithUtil.addTotal(v1.getAmount9(), v2.getAmount9()));
dto.setAmount10(ArithUtil.addTotal(v1.getAmount10(), v2.getAmount10()));
dto.setAmount11(ArithUtil.addTotal(v1.getAmount11(), v2.getAmount11()));
dto.setAmount12(ArithUtil.addTotal(v1.getAmount12(), v2.getAmount12()));
dto.setAmountSum(ArithUtil.addTotal(dto.getAmount1(), dto.getAmount2(), dto.getAmount3(), dto.getAmount4(), dto.getAmount5(), dto.getAmount6()
, dto.getAmount7(), dto.getAmount8(), dto.getAmount9(), dto.getAmount10(), dto.getAmount11(), dto.getAmount12()));
return dto;
}));
return l2GroupAmountMap;
}
方式三:利用Stream中的Collectors.reducing
/**
* 汇总求和Amount1-10
* @param baseReportAmountList
* @return
*/
private static Map<String, BudgetBaseReportDTO> getReportDTOMap2(List<BudgetBaseReportDTO> baseReportAmountList) {
Map<String, BudgetBaseReportDTO> l2GroupAmountMap = baseReportAmountList.stream()
.collect(Collectors.groupingBy((k -> k.getL2() + "#" + k.getBaseLineName() + "#" + k.getEmpType() + "#" + k.getCostSubject()),
Collectors.reducing(new BudgetBaseReportDTO(),
(v1, v2) -> {
BudgetBaseReportDTO dto = new BudgetBaseReportDTO();
dto.setEmpType(v1.getEmpType());
dto.setL1(v1.getL1());
dto.setL2(v1.getL2());
dto.setBaseLineName(v1.getBaseLineName());
dto.setCostSubject(v1.getCostSubject());
dto.setCostSubjectDesc(v1.getCostSubjectDesc());
dto.setAmount1(ArithUtil.addTotal(v1.getAmount1(), v2.getAmount1()));
dto.setAmount2(ArithUtil.addTotal(v1.getAmount2(), v2.getAmount2()));
dto.setAmount3(ArithUtil.addTotal(v1.getAmount3(), v2.getAmount3()));
dto.setAmount4(ArithUtil.addTotal(v1.getAmount4(), v2.getAmount4()));
dto.setAmount5(ArithUtil.addTotal(v1.getAmount5(), v2.getAmount5()));
dto.setAmount6(ArithUtil.addTotal(v1.getAmount6(), v2.getAmount6()));
dto.setAmount7(ArithUtil.addTotal(v1.getAmount7(), v2.getAmount7()));
dto.setAmount8(ArithUtil.addTotal(v1.getAmount8(), v2.getAmount8()));
dto.setAmount9(ArithUtil.addTotal(v1.getAmount9(), v2.getAmount9()));
dto.setAmount10(ArithUtil.addTotal(v1.getAmount10(), v2.getAmount10()));
dto.setAmount11(ArithUtil.addTotal(v1.getAmount11(), v2.getAmount11()));
dto.setAmount12(ArithUtil.addTotal(v1.getAmount12(), v2.getAmount12()));
dto.setAmountSum(ArithUtil.addTotal(dto.getAmount1(), dto.getAmount2(), dto.getAmount3(), dto.getAmount4(), dto.getAmount5(), dto.getAmount6()
, dto.getAmount7(), dto.getAmount8(), dto.getAmount9(), dto.getAmount10(), dto.getAmount11(), dto.getAmount12()));
return dto;
})));
return l2GroupAmountMap;
}
Collectors.reducing方法的详细说明:
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op)
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op)
public static <T, U> Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)
这些方法可以用于将流中的元素按照指定的方式进行归约操作,并返回一个 Optional 或最终的归约结果。
使用 Collectors.reducing 方法时,你需要提供一个二元操作符( BinaryOperator ),它定义了对两个元素进行归约的逻辑。如果你只提供了二元操作符,则归约的结果将被包装在一个 Optional 对象中。如果你还提供了一个起始值(identity),则归约的结果将直接返回,而不是一个 Optional 对象。
以下是一些示例:
// 示例1:使用二元操作符进行归约
Optional<Integer> sumOptional = list.stream()
.collect(Collectors.reducing((a, b) -> a + b));
// 示例2:使用二元操作符和起始值进行归约
int sum = list.stream()
.collect(Collectors.reducing(0, (a, b) -> a + b));
// 示例3:使用二元操作符、起始值和转换函数进行归约
String concatenated = list.stream()
.collect(Collectors.reducing("", String::concat, String::concat));
在示例1中,使用了二元操作符 (a, b) -> a + b 对整数列表进行求和,并将结果包装在一个 Optional 对象中。
在示例2中,使用了二元操作符 (a, b) -> a + b 和起始值 0 对整数列表进行求和,并返回了最终的归约结果。
在示例3中,使用了二元操作符 String::concat 、起始值 "" 和转换函数 String::concat 对字符串列表进行连接操作,并返回了最终的归约结果。
Q.E.D.