想必你們在項目中都有遇到把一個列表的多個字段累加求和的狀況,也就是一個列表的總計。有的童鞋問,這個不是給前端作的嗎?後端不是隻須要把列表返回就好了嘛。。。沒錯,我也是這樣想的,可是在一場和前端的撕逼大戰中敗下陣來以後,這個東西就落在我身上了。當時因爲工期緣由,時間比較緊,也就不考慮效率和易用性了,只是知足當時的需求,就隨便寫了個方法統計求和。目前稍微閒下來了,就把原來的代碼優化下。咱們先來看一下原來的代碼...前端
工具類java
import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * * @ClassName CalculationUtil * * @Description TODO(計算工具類) * * @Author 我恰芙蓉王 * * @Date 2020年04月21日 11:37 * * @Version 1.0.0 * **/ public class CalculationUtil { //拼接get set方法的常量 public static final String GET = "get"; public static final String SET = "set"; /** * 功能描述: 公用統計小計方法 * * @param list 原數據列表集合 * @param fields 運算的屬性數組 * @建立人: 我恰芙蓉王 * @建立時間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統計好的對象 **/ public static <T> T totalCalculationForBigDecimal(List<T> list, String... fields) throws Exception { if (CollectionUtils.isEmpty(list)) { return null; } Class clazz = list.get(0).getClass(); //返回值 Object object = clazz.newInstance(); list.stream().forEach(v -> Arrays.asList(fields).parallelStream().forEach(t -> { try { String field = StringUtils.capitalize(t); //獲取get方法 Method getMethod = clazz.getMethod(GET + field); //獲取set方法 Method setMethod = clazz.getMethod(SET + field, BigDecimal.class); Object objectValue = getMethod.invoke(object); setMethod.invoke(object, (objectValue == null ? BigDecimal.ZERO : (BigDecimal) objectValue).add((BigDecimal) getMethod.invoke(v))); } catch (Exception e) { e.printStackTrace(); } }) ); return (T) object; } /** * 功能描述: 公用統計小計方法 * * @param list 原數據列表集合 * @param fields 運算的屬性數組 * @建立人: 我恰芙蓉王 * @建立時間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統計好的對象 **/ public static <T> T totalCalculationForDouble(List<T> list, String... fields) throws Exception { if (CollectionUtils.isEmpty(list)) { return null; } Class clazz = list.get(0).getClass(); //返回值 Object object = clazz.newInstance(); list.stream().forEach(v -> Arrays.asList(fields).parallelStream().forEach(t -> { try { String field = StringUtils.capitalize(t); //獲取get方法 Method getMethod = clazz.getMethod(GET + field); //獲取set方法 Method setMethod = clazz.getMethod(SET + field, Double.class); Object objectValue = getMethod.invoke(object); setMethod.invoke(object, add((objectValue == null ? new Double(0) : (Double) objectValue), (Double) getMethod.invoke(v))); } catch (Exception e) { e.printStackTrace(); } }) ); return (T) object; } /** * 功能描述: 公用統計小計方法 * * @param list 原數據列表集合 * @param fields 運算的屬性數組 * @建立人: 我恰芙蓉王 * @建立時間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統計好的對象 **/ public static <T> T totalCalculationForFloat(List<T> list, String... fields) throws Exception { if (CollectionUtils.isEmpty(list)) { return null; } Class clazz = list.get(0).getClass(); //返回值 Object object = clazz.newInstance(); list.stream().forEach(v -> Arrays.asList(fields).parallelStream().forEach(t -> { try { String field = StringUtils.capitalize(t); //獲取get方法 Method getMethod = clazz.getMethod(GET + field); //獲取set方法 Method setMethod = clazz.getMethod(SET + field, Float.class); Object objectValue = getMethod.invoke(object); setMethod.invoke(object, add((objectValue == null ? new Float(0) : (Float) objectValue), (Float) getMethod.invoke(v))); } catch (Exception e) { e.printStackTrace(); } }) ); return (T) object; } /** * 提供精確的加法運算。 * * @param v1 被加數 * @param v2 加數 * @return 兩個參數的和 */ public static Double add(Double v1, Double v2) { BigDecimal b1 = new BigDecimal(v1.toString()); BigDecimal b2 = new BigDecimal(v2.toString()); return b1.add(b2).doubleValue(); } /** * 提供精確的加法運算。 * * @param v1 被加數 * @param v2 加數 * @return 兩個參數的和 */ public static Float add(Float v1, Float v2) { BigDecimal b1 = new BigDecimal(v1.toString()); BigDecimal b2 = new BigDecimal(v2.toString()); return b1.add(b2).floatValue(); } }
實體類redis
@Data @AllArgsConstructor @NoArgsConstructor public class Order { //訂單號 private String orderNo; //訂單金額 private Double money; //折扣 private Double discount; } @Data @AllArgsConstructor @NoArgsConstructor public class Phone { //手機名 private String name; //成本 private BigDecimal cost; //售價 private BigDecimal price; }
測試spring
public static void main(String[] args) throws Exception { List<Order> orderList = new ArrayList<Order>() { { add(new Order("D20111111", 256.45, 11.11)); add(new Order("D20111112", 123.85, 1.11)); add(new Order("D20111113", 546.13, 2.14)); add(new Order("D20111114", 636.44, 0.88)); } }; List<Phone> phoneList = new ArrayList<Phone>() { { add(new Phone("蘋果", new BigDecimal("123.11"), new BigDecimal("222.22"))); add(new Phone("三星", new BigDecimal("123.11"), new BigDecimal("222.22"))); add(new Phone("華爲", new BigDecimal("123.11"), new BigDecimal("222.22"))); add(new Phone("小米", new BigDecimal("123.11"), new BigDecimal("222.22"))); } }; Order orderTotal = totalCalculationForDouble(orderList, "money", "discount"); System.out.println("總計數據爲 :" + orderTotal); Phone phoneTotal = totalCalculationForBigDecimal(phoneList, "cost", "price"); System.out.println("總計數據爲 :" + phoneTotal); }
經過以上代碼能夠看出,效果是實現了,可是缺點也是很明顯的:apache
1.太過冗餘,相同代碼太多,多個方法只有少數代碼不相同(工具類中黃色標註的地方);後端
2.效率低,列表中每一個元素的每一個屬性都要用到反射賦值;api
3.靈活性不夠,要求實體類中須要參加運算的屬性都爲同一類型,即必須都爲Double,或必須都爲BigDecimal;數組
4.硬編碼,直接在方法調用時把實體類中的字段寫死,既不符合JAVA編碼規範也容易出錯,並且當該實體類中的屬性名變動的時候,IDE沒法提示咱們相應的傳參的變動,極容易踩坑。緩存
由於項目中用的JDK版本是1.8,當時在寫的時候就想經過方法引用規避掉這種硬編碼的方式,由於在Mybatis-Plus中也有用到方法引用賦值條件參數的狀況,但仍是由於時間緊急,就沒去研究了。tomcat
今天就順着這個方向去找了一下實現的方法,把代碼優化了部分,以下:
首先,我是想經過傳參爲方法引用的方式來獲取Getter方法對應的屬性名,經過了解,JDK8中已經給咱們提供了實現方式,首先聲明一個自定義函數式接口(須要實現Serializable)
@FunctionalInterface public interface SerializableFunction<T, R> extends Function<T, R>, Serializable { }
而後定義一個反射工具類去解析這個自定義函數式接口,在此工具類中有對方法引用解析的具體實現,在此類中規避掉缺點4
import org.apache.commons.lang3.StringUtils; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import java.lang.invoke.SerializedLambda; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @ClassName ReflectionUtil * @Description TODO(反射工具類) * @Author 我恰芙蓉王 * @Date 2020年09月08日 15:10 * @Version 2.0.0 **/ public class ReflectionUtil { public static final String GET = "get"; public static final String SET = "set"; /** * 功能描述: 經過get方法的方法引用返回對應的Field * * @param function * @建立人: 我恰芙蓉王 * @建立時間: 2020年09月08日 16:20:56 * @return: java.lang.reflect.Field **/ public static <T> Field getField(SerializableFunction<T, ?> function) { try { /** * 1.獲取SerializedLambda */ Method method = function.getClass().getDeclaredMethod("writeReplace"); method.setAccessible(Boolean.TRUE); /** * 2.利用jdk的SerializedLambda,解析方法引用,implMethodName 即爲Field對應的Getter方法名 */ SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function); //獲取get方法的方法名 String getter = serializedLambda.getImplMethodName(); //獲取屬性名 String fieldName = StringUtils.uncapitalize(getter.replace(GET, "")); /** * 3.獲取的Class是字符串,而且包名是「/」分割,須要替換成「.」,才能獲取到對應的Class對象 */ String declaredClass = serializedLambda.getImplClass().replace("/", "."); Class clazz = Class.forName(declaredClass, false, ClassUtils.getDefaultClassLoader()); /** * 4.經過Spring中的反射工具類獲取Class中定義的Field */ return ReflectionUtils.findField(clazz, fieldName); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } }
接着改寫原來計算工具類中的代碼,在此類中將原缺點的1,2,3點都規避了,將原來冗餘的多個方法精簡成一個 totalCalculation ,經過 methodMap 對象將get,set方法緩存(但此緩存還有優化的空間,能夠將方法中的緩存對象提到tomcat內存或redis中),經過動態獲取字段類型來實現不一樣類型的累加運算
import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import static io.renren.modules.test1.ReflectionUtil.GET; import static io.renren.modules.test1.ReflectionUtil.SET; /** * * @ClassName CalculationUtil * * @Description TODO(計算工具類) * * @Author 我恰芙蓉王 * * @Date 2020年04月21日 11:37 * * @Version 1.0.0 * **/ public class CalculationUtil { /** * 功能描述: 公用統計小計方法 * * @param list 原數據列表集合 * @param functions 參與運算的方法引用 * @建立人: 我恰芙蓉王 * @建立時間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統計好的對象 **/ public static <T> T totalCalculation(List<T> list, SerializableFunction<T, ?>... functions) throws Exception { if (CollectionUtils.isEmpty(list)) { return null; } //獲取集合中類型的class對象 Class clazz = list.get(0).getClass(); //Getter Setter緩存 Map<SerializableFunction, Map<String, Method>> methodMap = new ConcurrentHashMap<>(); //遍歷字段,將Getter Setter放入緩存中 for (SerializableFunction function : functions) { Field field = ReflectionUtil.getField(function); //獲取get方法 Method getMethod = clazz.getMethod(GET + StringUtils.capitalize(field.getName())); //獲取set方法 Method setMethod = clazz.getMethod(SET + StringUtils.capitalize(field.getName()), field.getType()); //將get set方法封裝成一個map放入緩存中 methodMap.put(function, new HashMap<String, Method>() { { put(GET, getMethod); put(SET, setMethod); } }); } //計算 T result = list.parallelStream().reduce((x, y) -> { try { Object newObject = x.getClass().newInstance(); Arrays.asList(functions).parallelStream().forEach(f -> { try { Map<String, Method> fieldMap = methodMap.get(f); //獲取緩存的get方法 Method getMethod = fieldMap.get(GET); //獲取緩存的set方法 Method setMethod = fieldMap.get(SET); //調用x參數t屬性的get方法 Object xValue = getMethod.invoke(x); //調用y參數t屬性的get方法 Object yValue = getMethod.invoke(y); //反射賦值到newObject對象 setMethod.invoke(newObject, add(xValue, yValue, getMethod.getReturnType())); } catch (Exception e) { e.printStackTrace(); } }); return (T) newObject; } catch (Exception e) { e.printStackTrace(); } return null; }).get(); return result; } /** * 功能描述: 提供精確的加法運算 * * @param v1 加數 * @param v2 被加數 * @param clazz 參數的class類型 * @建立人: 我恰芙蓉王 * @建立時間: 2020年09月08日 10:55:56 * @return: java.lang.Object 相加之和 **/ public static Object add(Object v1, Object v2, Class clazz) throws Exception { BigDecimal b1 = new BigDecimal(v1.toString()); BigDecimal b2 = new BigDecimal(v2.toString()); Constructor constructor = clazz.getConstructor(String.class); return constructor.newInstance(b1.add(b2).toString()); } }
測試實體類
@Data @AllArgsConstructor @NoArgsConstructor public class People { //名字 private String name; //年齡 private Integer age; //存款 private BigDecimal money; //身高 private Double height; }
調用
public static void main(String[] args) throws Exception { List<People> list = new ArrayList<People>() { { add(new People("張三", 18, BigDecimal.valueOf(10000), 168.45)); add(new People("李四", 20, BigDecimal.valueOf(20000), 155.68)); add(new People("王五", 25, BigDecimal.valueOf(30000), 161.54)); add(new People("趙六", 21, BigDecimal.valueOf(30000), 166.66)); } }; People total = CalculationUtil.totalCalculation(list, People::getAge, People::getMoney, People::getHeight); System.out.println("總計數據爲 :" + total); }
java8的lambda表達式確實極大的簡化了咱們的代碼,提升了編碼的效率,流計算更是使數據的運算變得高效快捷,也增長了代碼的可(zhuang)讀(bi)性。現在java14都出來了,但願在空餘時間也能多去了解一下新版本的新特性,而不能總是抱着(你發任你發,我用java8)的心態去學習,畢竟技術的更新迭代是極快的。
參考博文:https://blog.csdn.net/u013202238/article/details/105779686