在一般统计业务中,大都会有合计数据,即把需要累计的字段加起来。
@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();
}
}
- 利用反射形成合计数据
- 通过clazz.newInstance()构造合计数据行
- 循环处理每一个IFunction,可以看出1、2出代码就是上方获取aa、bb累计
- 通过get方法获取对应的set方法
- 给第一步构造的数据执行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);
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
/**
* @author herman_zhang
*/
public class CalculateUtil {
private CalculateUtil(){}
public static BigDecimal add(List<BigDecimal> numbs){
if(numbs == null || numbs.isEmpty()){
return null;
}
BigDecimal count = new BigDecimal("0.00");
for (BigDecimal num : numbs){
if(num == null){
continue;
}
count = count.add(num);
}
return count;
}
public static BigDecimal add(BigDecimal... numbs){
if (numbs == null || numbs.length == 0){
return null;
}
return add(Arrays.asList(numbs));
}
public static BigDecimal subtract(BigDecimal num1, BigDecimal num2,int newScale, RoundingMode roundingMode){
return num1.subtract(num2).setScale(newScale,roundingMode);
}
public static BigDecimal subtract(BigDecimal num1, BigDecimal num2){
return num1.subtract(num2);
}
public static BigDecimal multiply(BigDecimal num1, BigDecimal num2,int newScale, RoundingMode roundingMode){
return num1.multiply(num2).setScale(newScale,roundingMode);
}
public static BigDecimal multiply(BigDecimal num1, BigDecimal num2){
return multiply(num1,num2,2,RoundingMode.HALF_UP);
}
public static BigDecimal divide(BigDecimal num1, BigDecimal num2,int newScale, RoundingMode roundingMode){
return num1.divide(num2,newScale,roundingMode);
}
public static BigDecimal divide(BigDecimal num1, BigDecimal num2){
return divide(num1,num2, 2, RoundingMode.HALF_UP);
}
注意:方案二目前不支持基本类型的合计,请使用包装类,且支持的Number子类需根据NumberUtil 中情况而定,目前只支持Integer、BigDecimal 、Long,其它子类型自行补充,因没想出更好的统一方案,有统一方案的可以写在评论区,谢谢!