使用java8的方法引用替换硬编码的示例代码
背景
想必大家在项目中都有遇到把一个列表的多个字段累加求和的情况,也就是一个列表的总计。有的童鞋问,这个不是给前端做的吗?后端不是只需要把列表返回就行了嘛。。。没错,我也是这样想的,但是在一场和前端的撕逼大战中败下阵来之后,这个东西就落在我身上了。当时由于工期原因,时间比较紧,也就不考虑效率和易用性了,只是满足当时的需求,就随便写了个方法统计求和。目前稍微闲下来了,就把原来的代码优化下。我们先来看一下原来的代码...
原代码
工具类
importorg.apache.commons.lang3.StringUtils;
importorg.springframework.util.CollectionUtils;
importjava.lang.reflect.Method;
importjava.math.BigDecimal;
importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.List;
/**
**@ClassNameCalculationUtil
**@DescriptionTODO(计算工具类)
**@Author我恰芙蓉王
**@Date2020年04月21日11:37
**@Version1.0.0
*
**/
publicclassCalculationUtil{
//拼接getset方法的常量
publicstaticfinalStringGET="get";
publicstaticfinalStringSET="set";
/**
*功能描述:公用统计小计方法
*
*@paramlist原数据列表集合
*@paramfields运算的属性数组
*@创建人:我恰芙蓉王
*@创建时间:2020年05月12日17:50:09
*@return:org.apache.poi.ss.formula.functions.T返回统计好的对象
**/
publicstaticTtotalCalculationForBigDecimal(Listlist,String...fields)throwsException{
if(CollectionUtils.isEmpty(list)){
returnnull;
}
Classclazz=list.get(0).getClass();
//返回值
Objectobject=clazz.newInstance();
list.stream().forEach(v->
Arrays.asList(fields).parallelStream().forEach(t->{
try{
Stringfield=StringUtils.capitalize(t);
//获取get方法
MethodgetMethod=clazz.getMethod(GET+field);
//获取set方法
MethodsetMethod=clazz.getMethod(SET+field,BigDecimal.class);
ObjectobjectValue=getMethod.invoke(object);
setMethod.invoke(object,(objectValue==null?BigDecimal.ZERO:(BigDecimal)objectValue).add((BigDecimal)getMethod.invoke(v)));
}catch(Exceptione){
e.printStackTrace();
}
})
);
return(T)object;
}
/**
*功能描述:公用统计小计方法
*
*@paramlist原数据列表集合
*@paramfields运算的属性数组
*@创建人:我恰芙蓉王
*@创建时间:2020年05月12日17:50:09
*@return:org.apache.poi.ss.formula.functions.T返回统计好的对象
**/
publicstaticTtotalCalculationForDouble(Listlist,String...fields)throwsException{
if(CollectionUtils.isEmpty(list)){
returnnull;
}
Classclazz=list.get(0).getClass();
//返回值
Objectobject=clazz.newInstance();
list.stream().forEach(v->
Arrays.asList(fields).parallelStream().forEach(t->{
try{
Stringfield=StringUtils.capitalize(t);
//获取get方法
MethodgetMethod=clazz.getMethod(GET+field);
//获取set方法
MethodsetMethod=clazz.getMethod(SET+field,Double.class);
ObjectobjectValue=getMethod.invoke(object);
setMethod.invoke(object,add((objectValue==null?newDouble(0):(Double)objectValue),(Double)getMethod.invoke(v)));
}catch(Exceptione){
e.printStackTrace();
}
})
);
return(T)object;
}
/**
*功能描述:公用统计小计方法
*
*@paramlist原数据列表集合
*@paramfields运算的属性数组
*@创建人:我恰芙蓉王
*@创建时间:2020年05月12日17:50:09
*@return:org.apache.poi.ss.formula.functions.T返回统计好的对象
**/
publicstaticTtotalCalculationForFloat(Listlist,String...fields)throwsException{
if(CollectionUtils.isEmpty(list)){
returnnull;
}
Classclazz=list.get(0).getClass();
//返回值
Objectobject=clazz.newInstance();
list.stream().forEach(v->
Arrays.asList(fields).parallelStream().forEach(t->{
try{
Stringfield=StringUtils.capitalize(t);
//获取get方法
MethodgetMethod=clazz.getMethod(GET+field);
//获取set方法
MethodsetMethod=clazz.getMethod(SET+field,Float.class);
ObjectobjectValue=getMethod.invoke(object);
setMethod.invoke(object,add((objectValue==null?newFloat(0):(Float)objectValue),(Float)getMethod.invoke(v)));
}catch(Exceptione){
e.printStackTrace();
}
})
);
return(T)object;
}
/**
*提供精确的加法运算。
*
*@paramv1被加数
*@paramv2加数
*@return两个参数的和
*/
publicstaticDoubleadd(Doublev1,Doublev2){
BigDecimalb1=newBigDecimal(v1.toString());
BigDecimalb2=newBigDecimal(v2.toString());
returnb1.add(b2).doubleValue();
}
/**
*提供精确的加法运算。
*
*@paramv1被加数
*@paramv2加数
*@return两个参数的和
*/
publicstaticFloatadd(Floatv1,Floatv2){
BigDecimalb1=newBigDecimal(v1.toString());
BigDecimalb2=newBigDecimal(v2.toString());
returnb1.add(b2).floatValue();
}
}      
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassOrder{
//订单号
privateStringorderNo;
//订单金额
privateDoublemoney;
//折扣
privateDoublediscount;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassPhone{
//手机名
privateStringname;
//成本
privateBigDecimalcost;
//售价
privateBigDecimalprice;
}
测试
publicstaticvoidmain(String[]args)throwsException{
ListorderList=newArrayList(){
{
add(newOrder("D20111111",256.45,11.11));
add(newOrder("D20111112",123.85,1.11));
add(newOrder("D20111113",546.13,2.14));
add(newOrder("D20111114",636.44,0.88));
}
};
ListphoneList=newArrayList(){
{
add(newPhone("苹果",newBigDecimal("123.11"),newBigDecimal("222.22")));
add(newPhone("三星",newBigDecimal("123.11"),newBigDecimal("222.22")));
add(newPhone("华为",newBigDecimal("123.11"),newBigDecimal("222.22")));
add(newPhone("小米",newBigDecimal("123.11"),newBigDecimal("222.22")));
}
};
OrderorderTotal=totalCalculationForDouble(orderList,"money","discount");
System.out.println("总计数据为:"+orderTotal);
PhonephoneTotal=totalCalculationForBigDecimal(phoneList,"cost","price");
System.out.println("总计数据为:"+phoneTotal);
}    
通过以上代码可以看出,效果是实现了,但是缺点也是很明显的:
1.太过冗余,相同代码太多,多个方法只有少数代码不相同(工具类中黄色标注的地方);
2.效率低,列表中每个元素的每个属性都要用到反射赋值;
3.灵活性不够,要求实体类中需要参加运算的属性都为同一类型,即必须都为Double,或必须都为BigDecimal;
4.硬编码,直接在方法调用时把实体类中的字段写死,既不符合JAVA编码规范也容易出错,而且当该实体类中的属性名变更的时候,IDE无法提示我们相应的传参的变更,极容易踩坑。
因为项目中用的JDK版本是1.8,当时在写的时候就想通过方法引用规避掉这种硬编码的方式,因为在Mybatis-Plus中也有用到方法引用赋值条件参数的情况,但还是因为时间紧急,就没去研究了。
今天就顺着这个方向去找了一下实现的方法,把代码优化了部分,如下:
优化后
首先,我是想通过传参为方法引用的方式来获取Getter方法对应的属性名,通过了解,JDK8中已经给我们提供了实现方式,首先声明一个自定义函数式接口(需要实现Serializable)
@FunctionalInterface publicinterfaceSerializableFunctionextendsFunction ,Serializable{ } 
然后定义一个反射工具类去解析这个自定义函数式接口,在此工具类中有对方法引用解析的具体实现,在此类中规避掉缺点4
importorg.apache.commons.lang3.StringUtils;
importorg.springframework.util.ClassUtils;
importorg.springframework.util.ReflectionUtils;
importjava.lang.invoke.SerializedLambda;
importjava.lang.reflect.Field;
importjava.lang.reflect.Method;
/**
*@ClassNameReflectionUtil
*@DescriptionTODO(反射工具类)
*@Author我恰芙蓉王
*@Date2020年09月08日15:10
*@Version2.0.0
**/
publicclassReflectionUtil{
publicstaticfinalStringGET="get";
publicstaticfinalStringSET="set";
/**
*功能描述:通过get方法的方法引用返回对应的Field
*
*@paramfunction
*@创建人:我恰芙蓉王
*@创建时间:2020年09月08日16:20:56
*@return:java.lang.reflect.Field
**/
publicstaticFieldgetField(SerializableFunctionfunction){
try{
/**
*1.获取SerializedLambda
*/
Methodmethod=function.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
/**
*2.利用jdk的SerializedLambda,解析方法引用,implMethodName即为Field对应的Getter方法名
*/
SerializedLambdaserializedLambda=(SerializedLambda)method.invoke(function);
//获取get方法的方法名
Stringgetter=serializedLambda.getImplMethodName();
//获取属性名
StringfieldName=StringUtils.uncapitalize(getter.replace(GET,""));
/**
*3.获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
*/
StringdeclaredClass=serializedLambda.getImplClass().replace("/",".");
Classclazz=Class.forName(declaredClass,false,ClassUtils.getDefaultClassLoader());
/**
*4.通过Spring中的反射工具类获取Class中定义的Field
*/
returnReflectionUtils.findField(clazz,fieldName);
}catch(ReflectiveOperationExceptione){
thrownewRuntimeException(e);
}
}
}  
接着改写原来计算工具类中的代码,在此类中将原缺点的1,2,3点都规避了,将原来冗余的多个方法精简成一个totalCalculation,通过methodMap对象将get,set方法缓存(但此缓存还有优化的空间,可以将方法中的缓存对象提到tomcat内存或redis中),通过动态获取字段类型来实现不同类型的累加运算
importorg.apache.commons.lang3.StringUtils;
importorg.springframework.util.CollectionUtils;
importjava.lang.reflect.Constructor;
importjava.lang.reflect.Field;
importjava.lang.reflect.Method;
importjava.math.BigDecimal;
importjava.util.*;
importjava.util.concurrent.ConcurrentHashMap;
importstaticio.renren.modules.test1.ReflectionUtil.GET;
importstaticio.renren.modules.test1.ReflectionUtil.SET;
/**
**@ClassNameCalculationUtil
**@DescriptionTODO(计算工具类)
**@Author我恰芙蓉王
**@Date2020年04月21日11:37
**@Version1.0.0
*
**/
publicclassCalculationUtil{
/**
*功能描述:公用统计小计方法
*
*@paramlist原数据列表集合
*@paramfunctions参与运算的方法引用
*@创建人:我恰芙蓉王
*@创建时间:2020年05月12日17:50:09
*@return:org.apache.poi.ss.formula.functions.T返回统计好的对象
**/
publicstaticTtotalCalculation(Listlist,SerializableFunction...functions)throwsException{
if(CollectionUtils.isEmpty(list)){
returnnull;
}
//获取集合中类型的class对象
Classclazz=list.get(0).getClass();
//GetterSetter缓存
Map>methodMap=newConcurrentHashMap<>();
//遍历字段,将GetterSetter放入缓存中
for(SerializableFunctionfunction:functions){
Fieldfield=ReflectionUtil.getField(function);
//获取get方法
MethodgetMethod=clazz.getMethod(GET+StringUtils.capitalize(field.getName()));
//获取set方法
MethodsetMethod=clazz.getMethod(SET+StringUtils.capitalize(field.getName()),field.getType());
//将getset方法封装成一个map放入缓存中
methodMap.put(function,newHashMap(){
{
put(GET,getMethod);
put(SET,setMethod);
}
});
}
//计算
Tresult=list.parallelStream().reduce((x,y)->{
try{
ObjectnewObject=x.getClass().newInstance();
Arrays.asList(functions).parallelStream().forEach(f->{
try{
MapfieldMap=methodMap.get(f);
//获取缓存的get方法
MethodgetMethod=fieldMap.get(GET);
//获取缓存的set方法
MethodsetMethod=fieldMap.get(SET);
//调用x参数t属性的get方法
ObjectxValue=getMethod.invoke(x);
//调用y参数t属性的get方法
ObjectyValue=getMethod.invoke(y);
//反射赋值到newObject对象
setMethod.invoke(newObject,add(xValue,yValue,getMethod.getReturnType()));
}catch(Exceptione){
e.printStackTrace();
}
});
return(T)newObject;
}catch(Exceptione){
e.printStackTrace();
}
returnnull;
}).get();
returnresult;
}
/**
*功能描述:提供精确的加法运算
*
*@paramv1加数
*@paramv2被加数
*@paramclazz参数的class类型
*@创建人:我恰芙蓉王
*@创建时间:2020年09月08日10:55:56
*@return:java.lang.Object相加之和
**/
publicstaticObjectadd(Objectv1,Objectv2,Classclazz)throwsException{
BigDecimalb1=newBigDecimal(v1.toString());
BigDecimalb2=newBigDecimal(v2.toString());
Constructorconstructor=clazz.getConstructor(String.class);
returnconstructor.newInstance(b1.add(b2).toString());
}
}      
测试实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
publicclassPeople{
//名字
privateStringname;
//年龄
privateIntegerage;
//存款
privateBigDecimalmoney;
//身高
privateDoubleheight;
}
调用
publicstaticvoidmain(String[]args)throwsException{
Listlist=newArrayList(){
{
add(newPeople("张三",18,BigDecimal.valueOf(10000),168.45));
add(newPeople("李四",20,BigDecimal.valueOf(20000),155.68));
add(newPeople("王五",25,BigDecimal.valueOf(30000),161.54));
add(newPeople("赵六",21,BigDecimal.valueOf(30000),166.66));
}
};
Peopletotal=CalculationUtil.totalCalculation(list,People::getAge,People::getMoney,People::getHeight);
System.out.println("总计数据为:"+total);
}  
总结
java8的lambda表达式确实极大的简化了我们的代码,提高了编码的效率,流计算更是使数据的运算变得高效快捷,也增加了代码的可(zhuang)读(bi)性。如今java14都出来了,希望在空余时间也能多去了解一下新版本的新特性,而不能老是抱着(你发任你发,我用java8)的心态去学习,毕竟技术的更新迭代是极快的。
到此这篇关于使用java8的方法引用替换硬编码的示例代码的文章就介绍到这了,更多相关java8的方法引用替换硬编码内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。