程序员

万能判空工具类,从此跟NullPointerException说

2018-04-08  本文已影响380人  PenguinMan

前言

本着“书同文,车同轨”的理念,阿里巴巴在2018年3月发布了《阿里巴巴安卓开发手册》的正式版,于此同时阿里云也开放了安卓规约的考试认证。认证通道刚刚开通的时候价格还比较公道,考试资格购买仅需要0.99元,但随着大家对规约的热捧,认证考试的价格也水涨船高,最新的考试费已经达到了9.9元一次。在阿里规约给服务端开发的同学带来了这样一条福音“数据的判空由使用者来做判断”,从此开始了奉旨传空的幸福生活。

客户端该怎么做

起初,面对层层嵌套的Json体判空,我们也很无奈,例如下面的Json例子:

{
  "code": 0,
  "description": "string",
  "lastUpdateTime": 0,
  "payload": {
        "name": "",
        "age": "",
        "subject":{
              "chinese": {
                   "regularScore": "25",
                   "finalScore": "85"
                },
               "english": {
                   "regularScore": "27",
                   "finalScore": "76"
                },
               "history": {
                   "regularScore": "20",
                   "finalScore": "65"
               }
     }
  }
}

假如我们要使用历史平时成绩字段,我们最初的做法是这样的:

if(response.getPayload()!=null&&response.getPayload().getSubject()!=null&&response.getPayload().getSubject().getHistory()!=null&&response.getPayload().getSubject().getHistory().getRegularScore()!=nul){
  String historyRegularScore = response.getPayload().getSubject().getHistory().getRegularScore();
}

使用一个字段,需要经过四层判断,面对一些使用网络数据较多的页面,简直不要太头疼。面对多层级类字段的判空,我们就没有什么更简洁的办法么?

新的方案

我们都知道Java反射可以在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。利用反射的这一特性,我们有了一套新的方案,利用反射获取到数据Bean的属性并对属性为空的字段赋予默认值。
首先我们通过getDeclaredFields( )获取一个对象的所有属性,为了能够修改私有属性的值,我们需要对field设置setAccessible为true。通过field.getGenericType()我们可以获取到属性的类型,例如"class java.lang.String","class java.lang.Integer"和"class java.lang.Long"等。不难看出现在我们已经可以根据type对不同的属性赋予不同的初值了,利用field.set()即可。至此一个对基本数据类型的反射赋初值方法就完成了。
这样只能对基本类型进行赋初值的操作,对于嵌套对象属性却没有办法,我们接着往下看,对于class对象getGenericType()返回的是对应的class类型,我们可以通过Class.forName反射来实例化对应该对象,并利用上面对基本类型赋初值的方法使用递归来对其进行层层赋初值操作。

                            type = type.replace("class ", "");
                            Class<?> cls = Class.forName(type);
                            Constructor<?> constructor = cls.getDeclaredConstructor();
                            //根据构造函数,传入值生成实例
                            Object objectInner = constructor.newInstance();
                            field.set(object, objectInner);
                            //递归实现内部Bean的初始化空值
                            convertData(objectInner);

完整代码实现

/**
 * Author : YangHaoYi on 2017/8/9.
 * Email  :  yang.haoyi@qq.com
 * Description :数据判空工具类,传入Bean null自动返回默认值
 * Change : YangHaoYi on 2017/8/9.
 * Version : V 1.0
 */
public class InitDataUtil {

    private static final String STRING = "class java.lang.String";
    private static final String INTEGER = "class java.lang.Integer";
    private static final String LONG = "class java.lang.Long";
    private static final String DOUBLE = "class java.lang.Double";
    private static final String BOOLEAN = "class java.lang.Boolean";


    public static void convertData(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        // 遍历所有属性
        for (Field field : fields) {
            //赋予修改私有属性权限
            field.setAccessible(true);
            //获取属性的类型
            String type = field.getGenericType().toString();
            //获取属性注解
            FieldMeta meta = field.getAnnotation(FieldMeta.class);
            //前面包含"class ",后面跟类名
            try {
                if (field.get(object) == null) {
                    switch (type) {
                        case STRING:
                            field.set(object,meta!=null?meta.defaultString():"");
                            break;
                        case INTEGER:
                            field.set(object, meta!=null?meta.defaultInt():0);
                            break;
                        case LONG:
                            field.set(object, meta!=null?meta.defaultLong():0);
                            break;
                        case DOUBLE:
                            field.set(object, meta!=null?meta.defaultDouble():0);
                            break;
                        case BOOLEAN:
                            field.set(object, meta!=null&&meta.defaultBoolean());
                            break;
                        default:
                            if(type.contains("java.util.List")){
                                break;
                            }
                            type = type.replace("class ", "");
                            Class<?> cls = Class.forName(type);
                            Constructor<?> constructor = cls.getDeclaredConstructor();
                            //根据构造函数,传入值生成实例
                            Object objectInner = constructor.newInstance();
                            field.set(object, objectInner);
                            //递归实现内部Bean的初始化空值
                            convertData(objectInner);
                            break;
                    }
                }

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

当然有很多时候我们对变量初值是有定制化需求的,对于可定制的初值,这里采取的是通过自定义注解的方式获取即getAnnotation(FieldMeta.class),如果注解不是空,则取注解的值作为初值,如果为空,这赋予默认初值。注解实现类如下:

/**
 * Author : YangHaoYi on 2017/8/10.
 * Email  :  yang.haoyi@qq.com
 * Description :数据判空注解辅助类
 * Change : YangHaoYi on 2017/8/10.
 * Version : V 1.0
 */
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Target({ElementType.FIELD, ElementType.METHOD})
//说明该注解将被包含在javadoc中
@Documented
public @interface FieldMeta {

    int defaultInt() default 0;

    String defaultString() default "";

    long defaultLong() default 0L;

    double defaultDouble() default 0D;

    boolean defaultBoolean() default false;
}

使用方法

给数据Bean添加初值

/**
 * Author : YangHaoYi on 2017/9/19.
 * Email  :  yanghaoyi@qq.com
 * Description :充电网点数据Bean
 * Change : YangHaoYi on 2017/9/19.
 * Version : V 1.0
 */
public class ChargeBranchData extends ResponseDataBean<List<ChargeBranchData.PayLoad>> {
        // 状态:1 兴建,2 维护,4 运营,8 禁用
        @FieldMeta(defaultString="2")
        private String status;
        //网点是否联网,1:联网,2:未联网
        @FieldMeta(defaultString="1")
        private String onlineType;
        //网点的地址
        private String address;
       //省略部分代码
    }

使用工具类对代码进行赋初值

        //调用工具类
        InitDataUtil.convertData(branchData);
上一篇 下一篇

猜你喜欢

热点阅读