利用Java反射机制降低代码圈复杂度
简介
- 在实际的工作中,我遇到了项目里老代码存在圈复杂度过高的问题,在提交代码的时候通不过CI(代码检查)的Lizard复杂度检查,所以迫切需要解决这个问题,运用Java反射是一个很好的解决思路。
什么是Java反射
- Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。 一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把一个个组成部分映射成一个个对象。
- 我们知道Spring框架可以帮我们创建和管理对象。需要对象时,我们无需自己手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。
- Java反射需要用到Class类
- Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
为什么要用Java反射
- 一开始学习Java的时候,我们都是通过new一个对象然后再对其进行操作,如果每个对象有特定的属性值,那么对一种场景进行校验的时候,就需要创建多个对象,然后对每个对象进行校验,在代码量上就会体现的十分繁琐。
public class DeviceInfo {
private Integer id;
private String deviceName;
private String deviceType;
private String deviceId;
private String devicePort;
private String userName;
private String userId;
//此处省略get、set方法,下面的代码都是写在这一个类中
}
- 对于上面的DeviceInfo的实体类,有一个场景,需要在它与另一个对象进行比较前先校验部分属性值是否为空,即在.euqals()方法内加入校验的代码,如果按照每个属性校验一次的思路,则需要写很多代码,显得十分繁琐。
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DeviceInfo other = (DeviceInfo) obj;
boolean deviceNameResult = checkDeviceName(this.deviceName, other);
if (deviceNameResult == false)
return false;
boolean deviceIdResult = checkDeviceId(this.deviceId, other);
if (deviceIdResult == false)
return false;
boolean devicePortResult = checkDevicePort(this.devicePort, other);
if (devicePortResult == false)
return false;
boolean UserIdResult = checkUserId(this.userId, other);
if (devicePortResult == false)
return false;
return true;
}
/**
* 校验deviceName是否为空
*/
private boolean checkDeviceName(String deviceName, DeviceInfo other)
{
if (deviceName == null) {
if (other.deviceName != null)
return false;
}
else if (!deviceName.equals(other.deviceName))
return false;
return true;
}
/**
* 校验deviceId是否为空
*/
private boolean checkDeviceId(String deviceId, DeviceInfo other)
{
if (deviceId == null) {
if (other.deviceId != null)
return false;
}
else if (!deviceId.equals(other.deviceId))
return false;
return true;
}
/**
* 校验devicePort是否为空
*/
private boolean checkDevicePort(String devicePort, DeviceInfo other)
{
if (devicePort == null) {
if (other.devicePort != null)
return false;
}
else if (!devicePort.equals(other.devicePort))
return false;
return true;
}
/**
* 校验userId是否为空
*/
private boolean checkUserId(String userId, DeviceInfo other)
{
if (userId == null) {
if (other.userId != null)
return false;
}
else if (!userId.equals(other.userId))
return false;
return true;
}
- 上述代码每个需要校验的属性都要写一遍重复的代码,不仅十分繁琐,代码的圈复杂度也很高(一个方法中有很多个if)
怎么使用Java反射降低代码圈复杂度
- 使用class类
//获取类对应的字节码对象
Class<?> clazz = Class.forName("类的全路径");
//通过类的字节码对象获取类中的成员变量
Field[] fs = clazz.getFields();//field顾名思义:属性
//通过类对应的字节码对象获取类中的成员方法们
Method[] ms = clazz.getMethods();
//通过字节码对象获取目标类的构造方法们
Constructor<?>[] cs = clazz.getConstructors();
//遍历数组,获取类中的每个成员变量的具体信息
for(Field f : fs){
//通过本轮循环到的字段对象获取字段名
System.out.println(f.getName());
//通过本轮循环到的字段对象获取字段的类型
System.out.println(f.getType());
}
- 使用Class类中的forName()静态方法(最安全,性能最好)
场景实例
- 对于上面提到的场景,
- 我们先将需要校验的属性写入Set容器,更方便后面代码维护,需要添加哪个就在容器里面写入
/**
* 包含需要校验是否为空的参数
*/
private static final Set<String> includeFields = new HashSet<>();
static {
includeFields.add("deviceIp");
includeFields.add("deviceName");
includeFields.add("devicePort");
includeFields.add("userId");
}
- 利用Java反射将重复代码提取出来
/**
* 校验单个参数是否为空
*
* @param field
* @param DeviceInfo
* @return
*/
private boolean checkParam(Field field, DeviceInfo DeviceInfo) {
String fieldName = field.getName();
field.setAccessible(true);
try {
Object thisField = field.get(this);
Object otherField = field.get(DeviceInfo);
if (thisField == null) {
return otherField == null;
} else return thisField.equals(otherField);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return false;
}
/**
* 校验参数是否为空,有一个参数为空就返回false
*
* @param other
*/
private boolean check(DeviceInfo other) {
Field[] fields = DeviceInfo.class.getDeclaredFields();
for (Field field : fields) {
if (includeFields.contains(field.getName())) {
if (!checkParam(field, other)) {
return false;
}
}
}
return true;
}
- 然后在.equals()内调用check
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof DeviceInfo)) return false;
DeviceInfo that = (DeviceInfo) obj;
if (!check(that)) return false;
return true;
}
- 上述代码也用到了封装的思想,即把实现一个功能的代码封装成一个个的方法,而不是写在同一个方法内,这样能使代码解耦,具有更高的维护性和可读性(逻辑清晰)