Accumulate the values of the same attribute in the List collection to get a total data

In general statistical business, metropolises have aggregate data, that is, add up the fields that need to be accumulated.

 

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

For example, in the above class, if there is a list, the last line needs to accumulate the values ​​of aa and bb to form the total data

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));

General practice:

//获取所有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);

It can be seen that when there are few fields, the code is not too much, but if there are more than a dozen fields, the code will be very much and very boring, so is there a simple way? First give a conclusion, the answer is of course yes.

The first option:

  • Everyone who has used mybatis-plus knows that it is possible to get the lambda expression method name (mybatis-plus is to get the get method name, and then intercept to get the field name), below is a similar SFunction interface in mybatis-plus, the key point is It is serialization, you can read the SerializedLambda class documentation.
@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();
    }
}
  • Use reflection to form aggregate data
  1. Construct a total data row through clazz.newInstance()
  2. Processing each IFunction in a loop, you can see that the code 1, 2 is the above to get the accumulation of aa and bb
  3. Get the corresponding set method through the get method
  4. Perform the set method assignment to the data constructed in the first step
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;
}

Calling method: only need to pass in which fields to be totaled, the disadvantage is that the more total fields, the more parameters

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

The second option:

In the first scheme, we pass in the statistical fields. If the statistical field types are basically the same, for example, the amount statistics are all of the BigDecimal type, we can use the field type statistics

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);
    }
}

Calling method: pass in the type of the field that needs to be counted, which must be a subtype of Number

For example, the statistics above aa, bb, because they are all of BigDecimal type, so only pass in BigDecimal.class

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

What if cc of Integer type is to be counted?

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

 

Note : Scheme 2 currently does not support the total of basic types, please use the packaging class, and the number subclasses supported need to be determined according to the situation in NumberUtil, currently only supports Integer, BigDecimal, Long, other subtypes are supplemented by themselves, because I did not think of it A better unified plan, if there is a unified plan, you can write it in the comment area, thank you! 

Guess you like

Origin blog.csdn.net/sinat_33472737/article/details/105997060