您好,登錄后才能下訂單哦!
背景
想必大家在項(xiàng)目中都有遇到把一個列表的多個字段累加求和的情況,也就是一個列表的總計(jì)。有的童鞋問,這個不是給前端做的嗎?后端不是只需要把列表返回就行了嘛。。。沒錯,我也是這樣想的,但是在一場和前端的撕逼大戰(zhàn)中敗下陣來之后,這個東西就落在我身上了。當(dāng)時(shí)由于工期原因,時(shí)間比較緊,也就不考慮效率和易用性了,只是滿足當(dāng)時(shí)的需求,就隨便寫了個方法統(tǒng)計(jì)求和。目前稍微閑下來了,就把原來的代碼優(yōu)化下。我們先來看一下原來的代碼...
原代碼
工具類
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(計(jì)算工具類) * * @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"; /** * 功能描述: 公用統(tǒng)計(jì)小計(jì)方法 * * @param list 原數(shù)據(jù)列表集合 * @param fields 運(yùn)算的屬性數(shù)組 * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統(tǒng)計(jì)好的對象 **/ 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; } /** * 功能描述: 公用統(tǒng)計(jì)小計(jì)方法 * * @param list 原數(shù)據(jù)列表集合 * @param fields 運(yùn)算的屬性數(shù)組 * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統(tǒng)計(jì)好的對象 **/ 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; } /** * 功能描述: 公用統(tǒng)計(jì)小計(jì)方法 * * @param list 原數(shù)據(jù)列表集合 * @param fields 運(yùn)算的屬性數(shù)組 * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統(tǒng)計(jì)好的對象 **/ 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; } /** * 提供精確的加法運(yùn)算。 * * @param v1 被加數(shù) * @param v2 加數(shù) * @return 兩個參數(shù)的和 */ 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(); } /** * 提供精確的加法運(yùn)算。 * * @param v1 被加數(shù) * @param v2 加數(shù) * @return 兩個參數(shù)的和 */ 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(); } }
實(shí)體類
@Data @AllArgsConstructor @NoArgsConstructor public class Order { //訂單號 private String orderNo; //訂單金額 private Double money; //折扣 private Double discount; } @Data @AllArgsConstructor @NoArgsConstructor public class Phone { //手機(jī)名 private String name; //成本 private BigDecimal cost; //售價(jià) private BigDecimal price; }
測試
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("總計(jì)數(shù)據(jù)為 :" + orderTotal); Phone phoneTotal = totalCalculationForBigDecimal(phoneList, "cost", "price"); System.out.println("總計(jì)數(shù)據(jù)為 :" + phoneTotal); }
通過以上代碼可以看出,效果是實(shí)現(xiàn)了,但是缺點(diǎn)也是很明顯的:
1.太過冗余,相同代碼太多,多個方法只有少數(shù)代碼不相同(工具類中黃色標(biāo)注的地方);
2.效率低,列表中每個元素的每個屬性都要用到反射賦值;
3.靈活性不夠,要求實(shí)體類中需要參加運(yùn)算的屬性都為同一類型,即必須都為Double,或必須都為BigDecimal;
4.硬編碼,直接在方法調(diào)用時(shí)把實(shí)體類中的字段寫死,既不符合JAVA編碼規(guī)范也容易出錯,而且當(dāng)該實(shí)體類中的屬性名變更的時(shí)候,IDE無法提示我們相應(yīng)的傳參的變更,極容易踩坑。
因?yàn)轫?xiàng)目中用的JDK版本是1.8,當(dāng)時(shí)在寫的時(shí)候就想通過方法引用規(guī)避掉這種硬編碼的方式,因?yàn)樵贛ybatis-Plus中也有用到方法引用賦值條件參數(shù)的情況,但還是因?yàn)闀r(shí)間緊急,就沒去研究了。
今天就順著這個方向去找了一下實(shí)現(xiàn)的方法,把代碼優(yōu)化了部分,如下:
優(yōu)化后
首先,我是想通過傳參為方法引用的方式來獲取Getter方法對應(yīng)的屬性名,通過了解,JDK8中已經(jīng)給我們提供了實(shí)現(xiàn)方式,首先聲明一個自定義函數(shù)式接口(需要實(shí)現(xiàn)Serializable)
@FunctionalInterface public interface SerializableFunction<T, R> extends Function<T, R>, Serializable { }
然后定義一個反射工具類去解析這個自定義函數(shù)式接口,在此工具類中有對方法引用解析的具體實(shí)現(xiàn),在此類中規(guī)避掉缺點(diǎn)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方法的方法引用返回對應(yīng)的Field * * @param function * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 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對應(yīng)的Getter方法名 */ SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function); //獲取get方法的方法名 String getter = serializedLambda.getImplMethodName(); //獲取屬性名 String fieldName = StringUtils.uncapitalize(getter.replace(GET, "")); /** * 3.獲取的Class是字符串,并且包名是“/”分割,需要替換成“.”,才能獲取到對應(yīng)的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); } } }
接著改寫原來計(jì)算工具類中的代碼,在此類中將原缺點(diǎn)的1,2,3點(diǎn)都規(guī)避了,將原來冗余的多個方法精簡成一個 totalCalculation ,通過 methodMap 對象將get,set方法緩存(但此緩存還有優(yōu)化的空間,可以將方法中的緩存對象提到tomcat內(nèi)存或redis中),通過動態(tài)獲取字段類型來實(shí)現(xiàn)不同類型的累加運(yùn)算
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(計(jì)算工具類) * * @Author 我恰芙蓉王 * * @Date 2020年04月21日 11:37 * * @Version 1.0.0 * **/ public class CalculationUtil { /** * 功能描述: 公用統(tǒng)計(jì)小計(jì)方法 * * @param list 原數(shù)據(jù)列表集合 * @param functions 參與運(yùn)算的方法引用 * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 2020年05月12日 17:50:09 * @return: org.apache.poi.ss.formula.functions.T 返回統(tǒng)計(jì)好的對象 **/ 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); } }); } //計(jì)算 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); //調(diào)用x參數(shù)t屬性的get方法 Object xValue = getMethod.invoke(x); //調(diào)用y參數(shù)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; } /** * 功能描述: 提供精確的加法運(yùn)算 * * @param v1 加數(shù) * @param v2 被加數(shù) * @param clazz 參數(shù)的class類型 * @創(chuàng)建人: 我恰芙蓉王 * @創(chuàng)建時(shí)間: 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()); } }
測試實(shí)體類
@Data @AllArgsConstructor @NoArgsConstructor public class People { //名字 private String name; //年齡 private Integer age; //存款 private BigDecimal money; //身高 private Double height; }
調(diào)用
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("總計(jì)數(shù)據(jù)為 :" + total); }
總結(jié)
java8的lambda表達(dá)式確實(shí)極大的簡化了我們的代碼,提高了編碼的效率,流計(jì)算更是使數(shù)據(jù)的運(yùn)算變得高效快捷,也增加了代碼的可(zhuang)讀(bi)性。如今java14都出來了,希望在空余時(shí)間也能多去了解一下新版本的新特性,而不能老是抱著(你發(fā)任你發(fā),我用java8)的心態(tài)去學(xué)習(xí),畢竟技術(shù)的更新迭代是極快的。
到此這篇關(guān)于使用java8的方法引用替換硬編碼的示例代碼的文章就介紹到這了,更多相關(guān)java8的方法引用替換硬編碼內(nèi)容請搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。