将List集合中相同属性的数值累加得到一条合计数据

在一般统计业务中,大都会有合计数据,即把需要累计的字段加起来。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MagicList {
    private BigDecimal aa;
    private BigDecimal bb;
    private Integer cc;
    private String dd;
    private int ee;
}

比如上述类,如果有一个列表,最后一行需要把aa,bb的值累加起来形成合计数据

List<MagicList> magicLists = = new ArrayList<>();
magicLists.add(new MagicList(new BigDecimal("1"),new BigDecimal("11"),111,"1111",1));
magicLists.add(new MagicList(new BigDecimal("2"),new BigDecimal("22"),222,"2222",2));

一般做法:

//获取所有aa值  1
List<BigDecimal> aas = magicLists.stream().map(MagicList::getAa).collect(Collectors.toList());
//合计aa值      2
BigDecimal aSum = add(aas);
//获取所有bb值   3
List<BigDecimal> bbs = magicLists.stream().map(MagicList::getBb).collect(Collectors.toList());
//合计bb值       4
BigDecimal bSum = add(bbs);
//新建一条合计数据,复制合计的值  5
MagicList magicList = new MagicList();
magicList.setAa(aSum);
magicList.setBb(bSum);

可以看到在字段少的情况下,代码还不是很多,但如果有十几个字段呢,代码会非常多且很枯燥,那么有没有简单做法呢?先给出结论,答案当然是有。

第一种方案:

  • 用过mybatis-plus都知道,是可以获取到lambda表达式方法名的(mybatis-plus就是获取到get方法名,然后截取获取字段名称),下方是一个类似mybatis-plus中SFunction接口,关键点就在于序列化,可以看下SerializedLambda 类文档说明。
@FunctionalInterface
public interface IFunction<T, R> extends Function<T, R>, Serializable {

    default SerializedLambda getSerializedLambda(){
        Method write;
        try {
            write = this.getClass().getDeclaredMethod("writeReplace");
            write.setAccessible(true);
            return (SerializedLambda) write.invoke(this);
        } catch (Exception e) {
            throw new IllegalArgumentException();
        }
    }

    default String getImplClass() {
        return getSerializedLambda().getImplClass();
    }

    default String getImplMethodName() {
        return getSerializedLambda().getImplMethodName();
    }
}
  • 利用反射形成合计数据
  1. 通过clazz.newInstance()构造合计数据行
  2. 循环处理每一个IFunction,可以看出1、2出代码就是上方获取aa、bb累计
  3. 通过get方法获取对应的set方法
  4. 给第一步构造的数据执行set方法赋值
public static <T> T mapperSum(Class<T> clazz,List<T> list, IFunction<? super T, ? extends BigDecimal>... mappers){
   try{
       List<BigDecimal> data;
       T o = clazz.newInstance();
       for (IFunction<? super T, ? extends BigDecimal> mapper : mappers) {
           //1
           data = list.stream().map(mapper).filter(Objects::nonNull).collect(Collectors.toList());
           //2
           BigDecimal add = CalculateUtil.add(data);
           String setMethod = setMethod(mapper);
           Method method = clazz.getMethod(setMethod,BigDecimal.class);
           method.invoke(o,add);
       }
       return o;
   }catch (Exception e){
       throw new IllegalArgumentException();
   }

}
private static <T> String setMethod(IFunction<? super T, ? extends BigDecimal> func){
    String implMethodName = func.getImplMethodName();
    String substring = implMethodName.substring(3);
    return "set"+substring;
}

调用方式:只需要传入要合计哪些字段,缺点就是合计字段越多,参数就越多

MagicList magicList = mapperSum(MagicList.class,magicLists, MagicList::getAa, MagicList::getBb);

第二种方案:

第一种方案我们传入的是统计的字段,如果统计的字段类型基本一致,比如金额统计中的都是BigDecimal类型,我们可以通过字段类型统计

public static <T,E extends Number> T mapperSum2(List<T> list,Class<T> clazz,Class<?>... tarClasses){
    try{
        List<Class<?>> classes = Arrays.asList(tarClasses);
        Field[] declaredFields = clazz.getDeclaredFields();
        T o = clazz.newInstance();
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);
            Class<?> type = declaredField.getType();
            if (classes.contains(type)) {
                String fieldName = declaredField.getName();
                fieldName = firstLetterToUpper(fieldName);
                Method method = clazz.getMethod("get"+fieldName);
                List<E> data = new ArrayList<>();
                for (T t : list) {
                    E e = (E)method.invoke(t);
                    data.add(e);
                }
                E add = NumberUtil.add(data);
                String setMethodName =  "set"+fieldName;
                Method setMethod = clazz.getMethod(setMethodName,type);
                setMethod.invoke(o,add);
            }
        }
        return o;
    }catch (Exception e){
        throw new IllegalArgumentException();
    }

}
private static String firstLetterToUpper(String str){
    char[] array = str.toCharArray();
    array[0] -= 32;
    return String.valueOf(array);
}
public class NumberUtil {
    private NumberUtil() {
    }

    public static <T extends Number> T add(List<T> numbs) {
        if (numbs == null || numbs.isEmpty()) {
            return null;
        }
        Class<? extends Number> aClass = numbs.get(0).getClass();
        if (isBaseTypePackaging(aClass) || aClass.isPrimitive()) {
            Integer sumInteger = 0;
            Long sumLong = 0L;
            for (T numb : numbs) {
                if (numb == null) {
                    continue;
                }
                if (aClass.isAssignableFrom(Integer.class)) {
                    sumInteger += numb.intValue();
                } else if (aClass.isAssignableFrom(Long.class)) {
                    sumLong += numb.longValue();
                }
            }
            if (aClass.isAssignableFrom(Integer.class)) {
                return ( T ) sumInteger;
            } else if (aClass.isAssignableFrom(Long.class)) {
                return ( T ) sumLong;
            }
            return null;
        } else if (aClass.isAssignableFrom(BigDecimal.class)) {
            BigDecimal count = new BigDecimal("0.00");
            for (T numb : numbs) {
                if (numb == null) {
                    continue;
                }
                BigDecimal num = ( BigDecimal ) numb;
                count = count.add(num);
            }
            return ( T ) count;
        }
        return null;
    }

    /**
     * 是否是基本类型的包装类
     *
     * @param c 对象class类型
     * @return boolean true 是 ,false 否
     */
    private static boolean isBaseTypePackaging(Class c) {
        return c.equals(java.lang.Integer.class) || c.equals(java.lang.Byte.class) || c.equals(java.lang.Long.class) || c.equals(java.lang.Double.class) || c.equals(java.lang.Float.class) || c.equals(java.lang.Character.class) || c.equals(java.lang.Short.class) || c.equals(java.lang.Boolean.class);
    }
}

调用方式:传入需要统计的字段类型,必须是Number子类型

比如统计上方aa,bb,因为都是BigDecimal类型,所以只用传入BigDecimal.class

MagicList magicList = MagicListSupport.mapperSum2(magicLists,MagicList.class, BigDecimal.class);

如果还要统计Integer类型的cc呢

MagicList magicList = MagicListSupport.mapperSum2(magicLists,MagicList.class, BigDecimal.class, Integer.class);

注意:方案二目前不支持基本类型的合计,请使用包装类,且支持的Number子类需根据NumberUtil 中情况而定,目前只支持Integer、BigDecimal 、Long,其它子类型自行补充,因没想出更好的统一方案,有统一方案的可以写在评论区,谢谢! 

猜你喜欢

转载自blog.csdn.net/sinat_33472737/article/details/105997060