使⽤java8的⽅法引⽤替换硬编码
背景
想必⼤家在项⽬中都有遇到把⼀个列表的多个字段累加求和的情况,也就是⼀个列表的总计。有的童鞋问,这个不是给前端做的吗?后端不是只需要把列表返回就⾏了嘛。。。没错,我也是这样想的,但是在⼀场和前端的撕逼⼤战中败下阵来之后,这个东西就落在我⾝上了。当时由于⼯期原因,时间⽐较紧,也就不考虑效率和易⽤性了,只是满⾜当时的需求,就随便写了个⽅法统计求和。⽬前稍微闲下来了,就把原来的代码优化下。我们先来看⼀下原来的代码...
原代码
⼯具类
import org.apachemons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import flect.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, fields) throws Exception {
if (CollectionUtils.isEmpty(list)) {
return null;
}
Class clazz = (0).getClass();
//返回值
Object object = wInstance();
list.stream().forEach(v ->
Arrays.asList(fields).parallelStream().forEach(t -> {
try {
String field = StringUtils.capitalize(t);
//获取get⽅法
Method getMethod = Method(GET + field);
//获取set⽅法
Method setMethod = Method(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;
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, fields) throws Exception {
if (CollectionUtils.isEmpty(list)) {
return null;
}
Class clazz = (0).getClass();
//返回值
Object object = wInstance();
list.stream().forEach(v ->
Arrays.asList(fields).parallelStream().forEach(t -> {
try {
String field = StringUtils.capitalize(t);
//获取get⽅法
Method getMethod = Method(GET + field);
//获取set⽅法
Method setMethod = Method(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();
java replace方法}
})
);
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, fields) throws Exception {
if (CollectionUtils.isEmpty(list)) {
return null;
}
Class clazz = (0).getClass();
//返回值
Object object = wInstance();
list.stream().forEach(v ->
Arrays.asList(fields).parallelStream().forEach(t -> {
try {
String field = StringUtils.capitalize(t);
//获取get⽅法
Method getMethod = Method(GET + field);
//获取set⽅法
Method setMethod = Method(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;
return (T) object;
}
/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return两个参数的和
*/
public static Double add(Double v1, Double v2) { BigDecimal b1 = new String()); BigDecimal b2 = new String());
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return两个参数的和
*/
public static Float add(Float v1, Float v2) {
BigDecimal b1 = new String()); BigDecimal b2 = new String());
return b1.add(b2).floatValue();
}
}
实体类
@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;
}
测试
public static void main(String[] args) throws Exception {
List<Order> orderList = new ArrayList<Order>() {
{
add(new Order("D2*******", 256.45, 11.11));
add(new Order("D2*******", 123.85, 1.11));
add(new Order("D2*******", 546.13, 2.14));
add(new Order("D2*******", 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);
}
通过以上代码可以看出,效果是实现了,但是缺点也是很明显的:
1.太过冗余,相同代码太多,多个⽅法只有少数代码不相同(⼯具类中黄⾊标注的地⽅);
2.效率低,列表中每个元素的每个属性都要⽤到反射赋值;
3.灵活性不够,要求实体类中需要参加运算的属性都为同⼀类型,即必须都为Double,或必须都为BigDecimal;
4.硬编码,直接在⽅法调⽤时把实体类中的字段写死,既不符合JAVA编码规范也容易出错,⽽且当该实体类中的属性名变更的时候,IDE⽆法提⽰我们相应的传参的变更,极容易踩坑。
因为项⽬中⽤的JDK版本是1.8,当时在写的时候就想通过⽅法引⽤规避掉这种硬编码的⽅式,因为在Mybatis-Plus中也有⽤到⽅法引⽤赋值条件参数的情况,但还是因为时间紧急,就没去研究了。
今天就顺着这个⽅向去了⼀下实现的⽅法,把代码优化了部分,如下:
优化后
⾸先,我是想通过传参为⽅法引⽤的⽅式来获取Getter⽅法对应的属性名,通过了解,JDK8中已经给我们提供了实现⽅式,⾸先声明⼀个⾃定义函数式接⼝(需要实现Serializable)
@FunctionalInterface
public interface SerializableFunction<T, R> extends Function<T, R>, Serializable {
}
然后定义⼀个反射⼯具类去解析这个⾃定义函数式接⼝,在此⼯具类中有对⽅法引⽤解析的具体实现,在此类中规避掉缺点4
import org.apachemons.lang3.StringUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.invoke.SerializedLambda;
import flect.Field;
import flect.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: flect.Field
**/
public static <T> Field getField(SerializableFunction<T, ?> function) {
try {
/**
* 1.获取SerializedLambda
*/
Method method = Class().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
/**
* 2.利⽤jdk的SerializedLambda,解析⽅法引⽤,implMethodName 即为Field对应的Getter⽅法名
*/
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(function);
//获取get⽅法的⽅法名
String getter = ImplMethodName();
//获取属性名
String fieldName = StringUtils.place(GET, ""));
/**
* 3.获取的Class是字符串,并且包名是“/”分割,需要替换成“.”,才能获取到对应的Class对象
*/
String declaredClass = ImplClass().replace("/", ".");
Class clazz = Class.forName(declaredClass, false, DefaultClassLoader());
/**
* 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.apachemons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import flect.Constructor;
import flect.Field;
import flect.Method;
import java.math.BigDecimal;
import java.util.*;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论