• Android自定义注解实现一键校验实体类参数


    前言

    在阅读这篇文章前您需要先学会反射与类加载注解深入浅出这两项基础知识,关于这两项技术的解读就不赘述了,因为死记硬背的知识没什么好介绍的,大家可以自行百度。

    本文代码

    https://github.com/fzkf9225/mvvm-componnent-master/tree/master/common/src/main/java/pers/fz/mvvm/annotations

    需求

    首先我们想实现下图这样一个效果,一个表单,然后输入各种数据,最后点击按钮校验,在按钮下方输入结果,并且我们不用写一堆if条件语句判断输入是否合格
    在这里插入图片描述
    点击按钮校验

    binding.verifySubmit.setOnClickListener(v -> {
                showLoading("验证中...");
                VerifyResult verifyResult = EntityValidator.validate(binding.getData());
                hideLoading();
                if (verifyResult.isOk()) {
                    binding.tvVerifyResult.setTextColor(ContextCompat.getColor(this, pers.fz.mvvm.R.color.theme_green));
                } else {
                    binding.tvVerifyResult.setTextColor(ContextCompat.getColor(this, pers.fz.mvvm.R.color.theme_red));
                }
                showToast((verifyResult.isOk() ? "验证成功" : "验证失败:") + StringUtil.FilterNull(verifyResult.getErrorMsg()));
                binding.tvVerifyResult.setText(String.format("%s,验证结果:%s", binding.getData().toString(), verifyResult.getErrorMsg()));
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    是不是很方便简单,那么先了解反射与类加载注解深入浅出这两个知识点就可以开始撸码了。

    实现

    先假如我们需要验证这样一个实体类:

    public class Person{
    
        private String name;
    
        public Person() {
        }
    
        @Bindable
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们需要检验这个姓名:

    • 是否为空
    • 长度是否符合规律
    • 姓名中是否包含特殊字符,仅限中文汉字

    难么我们是不是需要写很多if判断,现在我们集合自定义注解去实现它

    新建自定义类注解

    在项目中新建一个包名,然后右键new ---->Java Class—>Annotation

    public @interface VerifyEntity {
    
    }
    
    • 1
    • 2
    • 3

    现在我们利用刚才所学的注解深入浅出得到的知识,给自定义注解添加系统提供的注解

    @Target(value = {ElementType.TYPE,ElementType.METHOD})//具体用法自行学习了,解释他没有意义
    @Retention(value = RetentionPolicy.RUNTIME) //运行时有效
    public @interface VerifyEntity {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后我们添加上开启验证方法

    @Target(value = {ElementType.TYPE,ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME) //运行时有效
    public @interface VerifyEntity {
    	//默认开启注解,则注解实体类时就不用写了,添加这个方法是便于一键关闭注解验证
        boolean enable() default true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    新建变量注解

    当然我们有了类注解没用呀,对吧。因为我们需要对实体类里面的字段进行验证。这时我们就需要用ElementType.FIELD枚举字段,系统提供给我们注解变量的。

    新建变量注解VerifyParams

    新建变量注解,并添加上系统提供的自定义注解解释器

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyParams {
     
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    给变量注解增添方法

    注解变量我们需要做什么呢?我们想起刚才的问题是不是
    那我们新增几个自定义方法

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyParams {
        boolean notNull() default false;
    	//注解参数如果不添加default那么就必须要在注解的时候指定值,我们加上默认值就可以不写,因为有的时候我们用不上
        int minLength() default -1;
    
        int maxLength() default -1;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    给实体类增加上注解

    @VerifyEntity
    public class Person{
    	@VerifyParams(minLength = 2, maxLength = 10,notNull = true),
        private String name;
    
        public Person() {
        }
    
        @Bindable
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    但是虽然有了注解但是没有实际效果,所以我们想一下,我们什么时机需要调用它?
    答案当然是:点击。当然我们还有更多的触发情况,但是总结一句:主动执行!
    那么我们就可以写一个方法取触发它

    新建验证方法EntityValidator

    这个时候我们需要用到我们上面学习的反射与类加载的知识了

    public class EntityValidator {
        private final static String TAG = EntityValidator.class.getSimpleName();
    	//传入实体类
        public static VerifyResult validate(Object entity) {
    		 /*
    		  * 实现代码再下面
    		  */
    		retrun VerifyResult.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们需要返回验证是否成功和提示文字,因此我们需要一个实体类

    public class VerifyResult {
        private boolean isSuccess;
    
        private String errorMsg;
    
        public VerifyResult() {
        }
    
        public VerifyResult(boolean isSuccess, String errorMsg) {
            this.isSuccess = isSuccess;
            this.errorMsg = errorMsg;
        }
    
        public boolean isOk() {
            return this.isSuccess;
        }
    
        public static VerifyResult ok() {
            return new VerifyResult(true, null);
        }
    
        public static VerifyResult ok(String errorMsg) {
            return new VerifyResult(true, errorMsg);
        }
    
        public static VerifyResult fail(String errorMsg) {
            return new VerifyResult(false, errorMsg);
        }
    
        public boolean isSuccess() {
            return isSuccess;
        }
    
        public void setSuccess(boolean success) {
            isSuccess = success;
        }
    
        public String getErrorMsg() {
            return errorMsg;
        }
    
        public void setErrorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
        }
    
        @Override
        public String toString() {
            return "VerifyResult{" +
                    "isSuccess=" + isSuccess +
                    ", errorMsg='" + errorMsg + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    那么现在我们需要获取到类注解了

    			//接上面注释部分代码
                Class<?> clazz = entity.getClass();
                VerifyEntity validation = clazz.getAnnotation(VerifyEntity.class);
                //如果获取不到则认为不需要验证,直接返回成功
                if (validation == null) {
                    return VerifyResult.ok();
                }
                //手动关闭验证的情况
                if (!validation.enable()) {
                    return VerifyResult.ok();
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果类需要验证那么我们接下来就需要获取字段属性的注解了,那么我们获取字段的注解VerifyParams

                Field[] fields = clazz.getDeclaredFields();
                if (fields.length == 0) {
                    return VerifyResult.ok();
                }
                for (Field field : fields) {
                	//如果当前字段没有注解则肯定不需要判断对吧,则执行下一个字段的判断
                    if (!field.isAnnotationPresent(VerifyParams.class)) {
                        continue;
                    }
                    //因为实体类字段都是private因此需要加上它,修改字段不可读写的属性
                    field.setAccessible(true);
                    VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
                    if (validationParam == null) {
                        return VerifyResult.ok();
                    }
                    //接下来就需要进行注解解析验证了
                }
                
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这个时候我们已经可以获取注解了,那么接下来干嘛?当然是解析注解

    解析注解字段

    接着上面往下写

    //接下来就需要进行注解解析验证了
    //一切的验证都是基于不为空的情况下,所以先验证空,我们先获取当前注解字段的值
    Object value = field.get(entity);
    if (validationParam .notNull() && value == null) {
    	return VerifyResult.fail("您的输入为空!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    写到这个时候我们听一下,我们这样提示给用户是不是会让用户丈二和尚摸不着头脑?但是我们又不能直接把字段名提示给用户,用户也看不懂,那么我们则可以给VerifyParams注解增加个方法

        /**
         * 错误提示信息
         * @return String
         */
        String errorMsg() default "信息填写错误,请验证后重新输入!";
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在使用注解的时候自定义提示内容,然后我们修改下上面的代码

    //接下来就需要进行注解解析验证了
    //一切的验证都是基于不为空的情况下,所以先验证空,我们先获取当前注解字段的值
    Object value = field.get(entity);
    if (validationParam.notNull() && value == null) {
    	return VerifyResult.fail(validationParam.errorMsg());
    }
    //然后我们再判断下长度
    //当最小长度和最大长度都未指定的时候则默认不判断
    if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
    	return VerifyResult.ok();
    }
    //分为几种情况,1、只指定最小值,2、只指定最大值,3、最大最小值都指定
    if (validationParams.minLength() < 0 && value.toString().length() >= validationParams.maxLength()) {
    	return VerifyResult.fail(validationParams.errorMsg());
    }
    if (validationParams.maxLength() < 0 && value.toString().length() <= validationParams.minLength()) {
    	return VerifyResult.fail(validationParams.errorMsg());
    }
    if (value.toString().length() >= validationParams.maxLength() || value.toString().length() <= validationParams.minLength()) {
    	return VerifyResult.fail(validationParams.errorMsg());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    查看效果

    VerifyResult verifyResult = EntityValidator.validate(binding.getData());
    //判断verifyResult结果
    
    • 1
    • 2

    其实这个时候你看效果已经实现了

    但是这样就结束了吗???当然没有!!!

    遗留问题

    我们实际的效果比这个复杂很多,而且上述代码也有很多问题,比如:

    • 空判断和长度判断共用了一个提示文字
    • 空判断只能判断null,但是有时候我们需要他可以为null有时候不可以,有时候不允许为null但可以为空字符串、空集合、空map等等
    • 是否可以支持判断输入内容与某个字符串相匹配才能通过校验
    • 是否可以支持判断输入内容是手机号码、电话号码、邮件、url等等
    • 是否可以支持判断数字的大小,范围
    • 是否可以支持判断有无保留两位有效数字
    • 是否可以支持判断正则

    那么我们需要解决几个问题:

    • 我们不用这么多判断共用一个errorMsg
    • 需要知道当前注解用于判断那种类型,是判断长度、数字、还是正则还是null
    • 一个字段上不用直接多条VerifyParams注解的问题,那我们怎么实现一个字段同时支持多个校验呢?

    思考问题

    我们如果想支持多个errorMsg和同时支持多个校验类型,那么我们会想到:数组、集合、map等等。但是注解只支持数组,那我们可以利用注解嵌套一个注解数组来实现多类型校验的效果。那么思路有了,我们来完善一下

    解决遗留问题

    新增注解方法

    我们先给VerifyParams注解增加一些需要的方法
    首先是校验类型

    public enum VerifyType {
        /**
         * 验证类型
         */
        NOTNULL,
        //与目标值是否相等
        EQUALS,
        //数字
        NUMBER,
        //整数
        NUMBER_INTEGER,
        //小数
        NUMBER_DOUBLE,
        //两位有效数字
        NUMBER_00,
        //email
        EMAIL,
        //手机号码、固话校验,不包括区号和固化
        PHONE,
        //手机号码校验,不包括区号和固化
        MOBILE_PHONE,
        //固话验,不包括区号和手机号码
        TEL_PHONE,
        //数字范围验证
        NUMBER_RANGE,
        //数字范围验证,包含最大最小值
        NUMBER_RANGE_EQUAL,
        //长度验证
        LENGTH_RANGE,
        //长度验证,包含最大最小值
        LENGTH_RANGE_EQUAL,
        //自定义正则
        REGEX,
    
        ;
    
        private String type;
    
        public String getType() {
            return type;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    新增一些校验方法字段

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyParams {
        VerifyType type();
    
        /**
         * 是否允许为空字符串、空集合、空map等
         * @return 默认可以为空
         */
        boolean notEmpty() default false;
    
        String equalStr() default "";
        boolean notNull() default false;
    
        int minLength() default -1;
    
        int maxLength() default -1;
    
        double minNumber() default -Double.MIN_VALUE;
    
        double maxNumber() default Double.MAX_VALUE;
    
        /**
         * 错误提示信息
         * @return String
         */
        String errorMsg() default "信息填写错误,请验证后重新输入!";
    
        /**
         * 正则表达式
         * @return 正则表达式字符串
         */
        String regex() default "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    新增注解数组来嵌套VerifyParams
    同样的new ---->Java Class—>Annotation

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyField {
        VerifyParams[] value();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们再校验时先获取注解

                    VerifyField validationField = field.getAnnotation(VerifyField.class);
                    VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
    
    • 1
    • 2

    但是有时候不需要多个类型注解,我们就可以两种注解同时混合用,但是再校验时把他们获取整合为一个整的数组,然后遍历判断校验

                    VerifyField validationField = field.getAnnotation(VerifyField.class);
                    VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
    
                    if (validationParam == null && validationField == null) {
                        return VerifyResult.ok();
                    }
                    VerifyParams[] verifyParamsList;
                    if (validationField == null) {
                        verifyParamsList = new VerifyParams[]{validationParam};
                    } else if (validationParam == null) {
                        verifyParamsList = validationField.value();
                    } else {
                        verifyParamsList = Arrays.copyOf(validationField.value(), validationField.value().length + 1);
                        verifyParamsList[verifyParamsList.length - 1] = validationParam;
                    }
                    for (VerifyParams params : verifyParamsList) {
    					//这个跟之前差不多了,也有点区别
    				}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    遍历VerifyParams 数组,我们首先需要知道当前注解属于什么类型

                    VerifyType verifyType = params.type();
                        if (verifyType == null) {
                            return VerifyResult.ok();
                        }
    
    • 1
    • 2
    • 3
    • 4

    分析空判断的情况

    因为我们所有的校验都和空有关因此,这个至关重要
    思考空判断分为几种情况:

    • 枚举VerifyType.NOTNULL和注解方法boolean notNull() default false;效果其实一样的,优先级最高,因为一切的校验都基于null
    • boolean notNull() default false;注解方法只是有的时候可以null和其他校验一起判断省一个注解语句,但是他的优先级要高于当前校验类型(枚举值不为VerifyType.NOTNULL
    • boolean notNull() default false;VerifyType.NOTNULL仅用户判断字段值是否为null并不适用于空字符串空集合空map等等
    • boolean notNull() default false;VerifyType.NOTNULL需要结合注解方法boolean notEmpty() default false;来实现空字符串空集合空map这些空实现的判断
    • 当校验类型不为VerifyType.NOTNULL时,但是 boolean notNull() default false;注解方法为false即可以为空时,即:当前字段允许为空,但是不为空的时候需要校验输入内容

    提前校验各种空值

    根据上面问题,那么我们先把空的几种情况判断了

                        VerifyType verifyType = params.type();
                        if (verifyType == null) {
                            return VerifyResult.ok();
                        }
                        //一切的验证都是基于不为空的情况下,所以先验证空
                        Object value = field.get(entity);
                        //当有VerifyType.NOTNULL、notNull为true时不管其他条件只要为空则返回错误
                        boolean isNullValue = (VerifyType.NOTNULL == verifyType || params.notNull()) && value == null;
                        if (isNullValue) {
                            return VerifyResult.fail(params.errorMsg());
                        }
                        //当不是NOTNULL但是又允许为空时,即判断某个条件时,有输入则判断,没有输入值则不判断
                        if (VerifyType.NOTNULL != verifyType && !params.notNull()) {
                            if (value == null) {
                                return VerifyResult.ok();
                            } else if (value instanceof Collection<?> collection && collection.size() == 0) {
                                return VerifyResult.ok();
                            } else if (value instanceof Map<?, ?> map && map.size() == 0) {
                                return VerifyResult.ok();
                            } else if (StringUtil.isEmpty(value)) {
                                return VerifyResult.ok();
                            }
                        }
                        //是否允许为空实现,空集合、空map等情况
                        if (params.notEmpty()) {
                            if (value instanceof Collection<?> collection && collection.size() == 0) {
                                return VerifyResult.fail(params.errorMsg());
                            } else if (value instanceof Map<?, ?> map && map.size() == 0) {
                                return VerifyResult.fail(params.errorMsg());
                            } else if (StringUtil.isEmpty(value)) {
                                return VerifyResult.fail(params.errorMsg());
                            }
                        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    但是上面判断还是有漏洞,主要来自于notNull()notEmpty() ,因为notEmpty() 为true时notNull()为false就不应该有效果,但是notEmpty() 为false时,notNull()为true时就需要有效果,所以我觉得这种情况还是自行约定吧,没必要写那么复杂的判断了

    实现各种校验类型

    空判断过去了,那么我们就可以获取校验类型了

    VerifyType verifyType = params.type();
    
    • 1

    然后就if判断各种类型,进行数据校验了,没什么技术问题,大家自己写吧

    关于空优先级的问题

    VerifyType.NOTNULL最高,主要单独使用;notNull()其次,主要结合其他的校验类型使用;notEmpty()最低,主要结合前两个字段用户判断是否允许为空实现,空字符串空集合空map他必须结合前两个来使用,单独使用不知道是否有问题,未经过大量测试。

    新的问题

    我们有了上面的优化后我们可以校验Person实体类了

    @VerifyEntity
    public class Person extends BaseObservable {
        @VerifyField({
                @VerifyParams(type = VerifyType.NOTNULL, notEmpty = true, errorMsg = "姓名为空!"),
                @VerifyParams(type = VerifyType.LENGTH_RANGE_EQUAL, minLength = 2, maxLength = 10, errorMsg = "姓名输入错误!"),
                @VerifyParams(type = VerifyType.EQUALS, errorMsg = "您只能填张三!", equalStr = "张三")
        })
        private String name;
    
        @VerifyField({
                @VerifyParams(type = VerifyType.NOTNULL, errorMsg = "请填写手机号码!"),
                @VerifyParams(type = VerifyType.MOBILE_PHONE, errorMsg = "手机号码格式输入不正确!")
        })
        private String mobile;
    
        @VerifyField({
                @VerifyParams(type = VerifyType.NOTNULL, errorMsg = "请填写固话号码!"),
                @VerifyParams(type = VerifyType.TEL_PHONE, errorMsg = "固话号码格式输入不正确!")
        })
        private String tel;
    
        @VerifyParams(type = VerifyType.NUMBER_RANGE, minNumber = 0, maxNumber = 120, errorMsg = "您是神仙吗?")
        private String age;
    
        @VerifyField({
                @VerifyParams(type = VerifyType.NOTNULL, notEmpty = true, errorMsg = "体重为空"),
                @VerifyParams(type = VerifyType.NUMBER_00, errorMsg = "体重输入格式不正确"),
                @VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, maxNumber = 200, errorMsg = "你该减肥了!!!"),
                @VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, minNumber = 40, errorMsg = "你已经瘦成竹竿了!!!")
        })
        private String weight;
        @VerifyField({
                @VerifyParams(type = VerifyType.NOTNULL, errorMsg = "身高为空"),
                @VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, maxNumber = 300, errorMsg = "姚明都没你高!!!"),
                @VerifyParams(type = VerifyType.NUMBER_RANGE_EQUAL, minNumber = 40, errorMsg = "建议您补补钙,多晒晒太阳!!!")
    
        })
        
        private String height;
        @VerifyField({
    //            @VerifyParams(type = VerifyType.NOTNULL, errorMsg = "邮箱地址为空!"),
                @VerifyParams(type = VerifyType.EMAIL, notNull = false, errorMsg = "邮箱地址错误!")
        })
        private String email;
    
        @VerifyParams(type = VerifyType.NOTNULL, notNull = true, errorMsg = "您填填写您的爱好!")
        private List<String> hobby;
    
        public Person() {
        }
    
        public Person(String name, String mobile, String tel, String age, String weight, String height, String email, List<String> hobby) {
            this.name = name;
            this.mobile = mobile;
            this.tel = tel;
            this.age = age;
            this.weight = weight;
            this.height = height;
            this.email = email;
            this.hobby = hobby;
        }
    
        @Bindable
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Bindable
        public String getAge() {
            return age;
        }
    
        public void setAge(String age) {
            this.age = age;
        }
    
        @Bindable
        public String getWeight() {
            return weight;
        }
    
        public void setWeight(String weight) {
            this.weight = weight;
        }
    
        @Bindable
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public List<String> getHobby() {
            return hobby;
        }
    
        public void setHobby(List<String> hobby) {
            this.hobby = hobby;
        }
    
        @Bindable
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    
        @Bindable
        public String getTel() {
            return tel;
        }
    
        public void setTel(String tel) {
            this.tel = tel;
        }
    
        @Bindable
        public String getHeight() {
            return height;
        }
    
        public void setHeight(String height) {
            this.height = height;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", mobile='" + mobile + '\'' +
                    ", tel='" + tel + '\'' +
                    ", age='" + age + '\'' +
                    ", weight='" + weight + '\'' +
                    ", height='" + height + '\'' +
                    ", email='" + email + '\'' +
                    ", hobby=" + hobby +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147

    但是实际测试你们会发现校验顺序是乱的,这是为什么?

    	这是因为Java编译器在编译过程中可能会对类的字段进行优化和重排序,
    	导致反射获取的字段顺序与源代码中的声明顺序不同
    
    • 1
    • 2

    这样就会导致一个问题,在我们校验一个表单的时候无法按照顺序去校验,比如我上面图中的表单,他就会先校验age字段,但是我姓名也没校验啊怎么就跳过去了呢?当然我们有一个很简单的办法去解决这个问题,那就是:重排序Field

    重新排序

    思考:我们如何排序?希望以什么形式去排序?
    答案:那肯定是我可以自定义顺序,而且醒目,并不是类文件的行数顺序
    这样我们就需要新增一个注解:

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyFieldSort {
    
        int value() default Integer.MAX_VALUE;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后在VerifyEntity中新增一个方法,用来开启关闭排序

        boolean sort() default false;
    
    • 1

    排序方法就没啥好说的了

        private static Field[] sortField(Field[] fields) {
            // 使用自定义注解的值进行排序
            Arrays.sort(fields, (f1, f2) -> {
                int order1 = getFieldOrder(f1);
                int order2 = getFieldOrder(f2);
                return Integer.compare(order1, order2);
            });
            return fields;
        }
    
        private static int getFieldOrder(Field field) {
            VerifyFieldSort verifyFieldSort = field.getAnnotation(VerifyFieldSort.class);
            if (verifyFieldSort != null) {
                return verifyFieldSort.value();
            }
            return Integer.MAX_VALUE; // 如果字段没有指定顺序,则将其放在最后
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在获取clazz.getDeclaredFields()后排个序

                Field[] fields = clazz.getDeclaredFields();
                if (fields.length == 0) {
                    return VerifyResult.ok();
                }
                if (validation.sort()) {
                    sortField(fields);
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在实体类上添加注解
    这样就可以按照我们的顺序来执行类

    @VerifyEntity(sort = true)
    public class Person extends BaseObservable {
        @VerifyFieldSort(1)
        private String name;
    
        @VerifyFieldSort(2)
        private String mobile;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    好了完成了。
    完整代码:
    VerifyEntity

    @Target(value = {ElementType.TYPE,ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME) //运行时有效
    public @interface VerifyEntity {
    
        boolean enable() default true;
    
        boolean sort() default false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    VerifyField

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyField {
        VerifyParams[] value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    VerifyFieldSort

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyFieldSort {
    
        int value() default Integer.MAX_VALUE;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    VerifyParams

    @Target(value = {ElementType.FIELD})
    @Retention(value = RetentionPolicy.RUNTIME)
    public @interface VerifyParams {
        VerifyType type();
    
        /**
         * 是否允许为空字符串、空集合、空map等
         * @return 默认可以为空
         */
        boolean notEmpty() default false;
    
        String equalStr() default "";
        boolean notNull() default false;
    
        int minLength() default -1;
    
        int maxLength() default -1;
    
        double minNumber() default -Double.MIN_VALUE;
    
        double maxNumber() default Double.MAX_VALUE;
    
        /**
         * 错误提示信息
         * @return String
         */
        String errorMsg() default "信息填写错误,请验证后重新输入!";
    
        /**
         * 正则表达式
         * @return 正则表达式字符串
         */
        String regex() default "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    VerifyResult

    public class VerifyResult {
        private boolean isSuccess;
    
        private String errorMsg;
    
        public VerifyResult() {
        }
    
        public VerifyResult(boolean isSuccess, String errorMsg) {
            this.isSuccess = isSuccess;
            this.errorMsg = errorMsg;
        }
    
        public boolean isOk() {
            return this.isSuccess;
        }
    
        public static VerifyResult ok() {
            return new VerifyResult(true, null);
        }
    
        public static VerifyResult ok(String errorMsg) {
            return new VerifyResult(true, errorMsg);
        }
    
        public static VerifyResult fail(String errorMsg) {
            return new VerifyResult(false, errorMsg);
        }
    
        public boolean isSuccess() {
            return isSuccess;
        }
    
        public void setSuccess(boolean success) {
            isSuccess = success;
        }
    
        public String getErrorMsg() {
            return errorMsg;
        }
    
        public void setErrorMsg(String errorMsg) {
            this.errorMsg = errorMsg;
        }
    
        @Override
        public String toString() {
            return "VerifyResult{" +
                    "isSuccess=" + isSuccess +
                    ", errorMsg='" + errorMsg + '\'' +
                    '}';
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    VerifyType

    public enum VerifyType {
        /**
         * 验证类型
         */
        NOTNULL,
        //与目标值是否相等
        EQUALS,
        //数字
        NUMBER,
        //整数
        NUMBER_INTEGER,
        //小数
        NUMBER_DOUBLE,
        //两位有效数字
        NUMBER_00,
        //email
        EMAIL,
        //手机号码、固话校验,不包括区号和固化
        PHONE,
        //手机号码校验,不包括区号和固化
        MOBILE_PHONE,
        //固话验,不包括区号和手机号码
        TEL_PHONE,
        //数字范围验证
        NUMBER_RANGE,
        //数字范围验证,包含最大最小值
        NUMBER_RANGE_EQUAL,
        //长度验证
        LENGTH_RANGE,
        //长度验证,包含最大最小值
        LENGTH_RANGE_EQUAL,
        //自定义正则
        REGEX,
    
        ;
    
        private String type;
    
        public String getType() {
            return type;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    EntityValidator

    public class EntityValidator {
        private final static String TAG = EntityValidator.class.getSimpleName();
    
        public static VerifyResult validate(Object entity) {
            try {
                Class<?> clazz = entity.getClass();
                VerifyEntity validation = clazz.getAnnotation(VerifyEntity.class);
                if (validation == null) {
                    return VerifyResult.ok();
                }
                if (!validation.enable()) {
                    return VerifyResult.ok();
                }
                // 无法保证获取的变量顺序与类文件中的声明顺序一致,是因为Java编译器在编译过程中可能会对类的字段进行优化和重排序,
                // 导致反射获取的字段顺序与源代码中的声明顺序不同
                // 排序只能另外自定义注解
                Field[] fields = clazz.getDeclaredFields();
                if (fields.length == 0) {
                    return VerifyResult.ok();
                }
                if (validation.sort()) {
                    sortField(fields);
                }
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(VerifyParams.class) && !field.isAnnotationPresent(VerifyField.class)) {
                        continue;
                    }
                    field.setAccessible(true);
                    VerifyField validationField = field.getAnnotation(VerifyField.class);
                    VerifyParams validationParam = field.getAnnotation(VerifyParams.class);
    
                    if (validationParam == null && validationField == null) {
                        return VerifyResult.ok();
                    }
                    VerifyParams[] verifyParamsList;
                    if (validationField == null) {
                        verifyParamsList = new VerifyParams[]{validationParam};
                    } else if (validationParam == null) {
                        verifyParamsList = validationField.value();
                    } else {
                        verifyParamsList = Arrays.copyOf(validationField.value(), validationField.value().length + 1);
                        verifyParamsList[verifyParamsList.length - 1] = validationParam;
                    }
                    for (VerifyParams params : verifyParamsList) {
                        VerifyType verifyType = params.type();
                        if (verifyType == null) {
                            return VerifyResult.ok();
                        }
                        //一切的验证都是基于不为空的情况下,所以先验证空
                        Object value = field.get(entity);
                        //当有VerifyType.NOTNULL、notNull为true时不管其他条件只要为空则返回错误
                        boolean isNullValue = (VerifyType.NOTNULL == verifyType || params.notNull()) && value == null;
                        if (isNullValue) {
                            return VerifyResult.fail(params.errorMsg());
                        }
                        //当不是NOTNULL但是又允许为空时,即判断某个条件时,有输入则判断,没有输入值则不判断
                        if (VerifyType.NOTNULL != verifyType && !params.notNull()) {
                            if (value == null) {
                                return VerifyResult.ok();
                            } else if (value instanceof Collection<?> collection && collection.size() == 0) {
                                return VerifyResult.ok();
                            } else if (value instanceof Map<?, ?> map && map.size() == 0) {
                                return VerifyResult.ok();
                            } else if (StringUtil.isEmpty(value)) {
                                return VerifyResult.ok();
                            }
                        }
                        //是否允许为空实现,空集合、空map等情况
                        if (params.notEmpty()) {
                            if (value instanceof Collection<?> collection && collection.size() == 0) {
                                return VerifyResult.fail(params.errorMsg());
                            } else if (value instanceof Map<?, ?> map && map.size() == 0) {
                                return VerifyResult.fail(params.errorMsg());
                            } else if (StringUtil.isEmpty(value)) {
                                return VerifyResult.fail(params.errorMsg());
                            }
                        }
                        VerifyResult verifyResult = verifyParam(params, value);
                        if (!verifyResult.isOk()) {
                            return verifyResult;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                LogUtil.e(TAG, "验证异常:" + e);
                return VerifyResult.ok("验证结果发生异常,将自动跳过验证!!!");
            }
            return VerifyResult.ok();
        }
    
        private static Field[] sortField(Field[] fields) {
            // 使用自定义注解的值进行排序
            Arrays.sort(fields, (f1, f2) -> {
                int order1 = getFieldOrder(f1);
                int order2 = getFieldOrder(f2);
                return Integer.compare(order1, order2);
            });
            return fields;
        }
    
        private static int getFieldOrder(Field field) {
            VerifyFieldSort verifyFieldSort = field.getAnnotation(VerifyFieldSort.class);
            if (verifyFieldSort != null) {
                return verifyFieldSort.value();
            }
            return Integer.MAX_VALUE; // 如果字段没有指定顺序,则将其放在最后
        }
    
        private static VerifyResult verifyParam(VerifyParams validationParams, Object value) {
            VerifyType verifyType = validationParams.type();
            if (verifyType == VerifyType.EQUALS) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!value.equals(validationParams.equalStr())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isNumber(value.toString()) && !RegexUtils.isDouble(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER_INTEGER) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isInteger(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER_DOUBLE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isDouble(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER_00) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isDoubleTwoDecimals(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.EMAIL) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isEmail(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.PHONE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isPhone(value.toString()) && !RegexUtils.isMobile(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.MOBILE_PHONE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isMobile(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.TEL_PHONE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (!RegexUtils.isPhone(value.toString())) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER_RANGE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                double number = Double.parseDouble(value.toString());
                if (number <= validationParams.minNumber() || number >= validationParams.maxNumber()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.NUMBER_RANGE_EQUAL) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                double number = Double.parseDouble(value.toString());
                if (number < validationParams.minNumber() || number > validationParams.maxNumber()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.LENGTH_RANGE) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
                    return VerifyResult.ok();
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.minLength() < 0 && value.toString().length() >= validationParams.maxLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.maxLength() < 0 && value.toString().length() <= validationParams.minLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (value.toString().length() >= validationParams.maxLength() || value.toString().length() <= validationParams.minLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.LENGTH_RANGE_EQUAL) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.minLength() < 0 && validationParams.maxLength() < 0) {
                    return VerifyResult.ok();
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.minLength() < 0 && value.toString().length() > validationParams.maxLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (validationParams.maxLength() < 0 && value.toString().length() < validationParams.minLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (value.toString().length() > validationParams.maxLength() || value.toString().length() < validationParams.minLength()) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
            } else if (verifyType == VerifyType.REGEX) {
                if (value == null) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                if (StringUtil.isEmpty(value)) {
                    return VerifyResult.fail(validationParams.errorMsg());
                }
                return RegexUtils.regular(value.toString(), validationParams.regex()) ?
                        VerifyResult.ok() : VerifyResult.fail(validationParams.errorMsg());
            }
    
            return VerifyResult.ok();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257

    写在最后

    代码未经大量测试,也没写太多复杂场景,大家可以自己根据思路修改,我应该讲的很清楚了

  • 相关阅读:
    世上没有白走的路,白走的路是在试错|MAC中import的maven工程无法识别java程序以及依赖包无法加载等问题的解决办法
    iview 酸爽debug: subMenu默认选中无效的解决方法
    工业设计:点石成金的“金手指”
    关于 硬盘
    python文字语音互转
    Linux中swap几乎耗尽,但物理内存还有空余的现象
    37~python 字符串
    【C语言】函数递归详解
    蓝桥杯13届JAVA A组 国赛
    Linux:Mac VMware Fusion13以及CentOS7安装包
  • 原文地址:https://blog.csdn.net/fzkf9225/article/details/132714575