百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

简简单单的反射和詹杜库放在一起就能好玩了吗 ?

toyiye 2024-08-22 23:09 5 浏览 0 评论

背景

相信大家在日常的工作中,一定遇到过以下的某个场景:

  • 前端需要选择某些字段,去展示不同字段下的信息,如果在字段和方法没有绑定的情况下,如何调用get方法呢?
  • 如果有一张横转纵的表,存储的do都是实际的字段名称,那么如何转化成实体类对应的get、set方法去执行操作呢?
  • 如果我想根据方法名称去调用对象的某个方法呢?

相信有些基础的程序员会立刻想到使用反射就好了,没错,就是这么简单,但是每用到一次,咱们就去写一次也是比较麻烦的,所以我们可以将它封装成工具类,用的时候直接去调用就好了。

实现

目前我在工具类实现了三个方法,分别是:

  • 根据属性名调用对象get方法
  • 根据属性名调用对象的set方法
  • 根据方法名调用方法

根据属性名调用对象get方法

实现逻辑:

  • 获取对象的class
  • 获取对象的所有属性
  • 获取对象的声明方法
  • 遍历属性
  • 匹配方法
  • invoke执行该方法
  • 返回方法的返回值
/**
         * 根据属性名获取属性值
         *
         * @return java.lang.String
         * @Param filedName
         * @Param obj
         * @Date 2022/12/15 15:06
         * @Author wjbgn
         **/
        public static String getObjField(String filedName, Object obj) {
            AtomicReference<String> value = new AtomicReference<>();
            Class<?> aClass = obj.getClass();
            // 获取所有属性
            Field[] declaredFields = aClass.getDeclaredFields();
            // 获取所有方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            Arrays.stream(declaredFields).forEach(filed -> {
                if (filed.getName().equals(filedName)) {
                    // 属性存在,尝试获取属性,调用get方法,此处get方法需要组装
                    Arrays.stream(declaredMethods).forEach(method -> {
                        if (method.getName().toLowerCase().equals("get" + filedName)) {
                            try {
                                // 执行方法
                                Object invoke = method.invoke(obj);
                                value.set(invoke == null ? null : invoke.toString());
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });
                }
            });
            return value.get();
        }

根据属性名调用对象的set方法

实现逻辑:

  • 获取对象的声明方法
  • 遍历方法
  • 匹配方法名称
  • invoke执行该方法
/**
         * 根据属性名设置属性值
         *
         * @return void
         * @Param filedName 字段名
         * @Param value 字段值
         * @Param obj 对象
         * @Date 2022/12/15 14:41
         * @Author wjbgn
         **/
        public static void setObjField(String filedName, String value, Object obj) {
            Class<?> aClass = obj.getClass();
            Arrays.stream(aClass.getDeclaredMethods()).forEach(method -> {
                if (method.getName().toLowerCase().equals("set" + filedName)) {
                    try {
                        method.invoke(obj, value);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }

根据方法名调用方法

实现逻辑:

  • 获取对象的所有方法 -> 不同于获取get、set,此处需要获取所有的方法,包括实现和继承来的。
  • 遍历方法
  • 匹配方法名称
  • invoke执行该方法
  • 返回方法返回值
/**
        * 根据方法名调用该方法
        *
        * @return java.lang.Object
        * @Param filedName
        * @Param obj
        * @Date 2022/12/15 15:05
        * @Author wjbgn
        **/
       public static Object invokeMethod(String filedName, Object obj) {
           AtomicReference<Object> result = new AtomicReference<>();
           Arrays.stream(obj.getClass().getMethods()).forEach(method -> {
               if (method.getName().toLowerCase().contains(filedName)) {
                   // 有方法包含该属性
                   try {
                       Object invoke = method.invoke(obj);
                       result.set(invoke);
                       return;
                   } catch (Exception e) {
                       throw new RuntimeException(e);
                   }
               }
           });
           return result.get();
       }

测试

准备基础代码

使用一段代码来测试下我们的方法,首先准备一些基础类。

背景是有三个小学生,分别是詹姆斯,库里,杜兰特,每个人共有一些属性,如下所示:

/**
    * 学生类,每个学生可以跑,跳,投篮
    */
   static class Student implements PlayerActon {

       private String name;

       private String age;

       private String team;

       @FieldDesc(type = "exclusive", value = " learn exclusive skills >> ")
       private String exclusive;
       @FieldDesc(value = "'s phone num? I don't know!")
       private String phone;

       public Student(String name, String age, String team, String phone) {
           this.name = name;
           this.age = age;
           this.team = team;
           this.phone = phone;
       }

       public Student() {

       }

       public String getExclusive() {
           return exclusive;
       }

       public void setExclusive(String exclusive) {
           this.exclusive = exclusive;
       }

       public String getName() {
           return name;
       }

       public void setName(String name) {
           this.name = name;
       }

       public String getAge() {
           return age;
       }

       public void setAge(String age) {
           this.age = age;
       }

       public String getTeam() {
           return team;
       }

       public void setTeam(String team) {
           this.team = team;
       }

       public String getPhone() {
           return phone;
       }

       public void setPhone(String phone) {
           this.phone = phone;
       }

       @Override
       public String running() {
           return " is running!";
       }

       @Override
       public String jumping() {
           return " is jumping!";
       }

       @Override
       public String shooting() {
           return " make a shot!";
       }
   }

上面的实体类实现了一个接口PlayerActon,里面是三个方法,表示运动员可以,,投篮:

    /**
    * 动作接口
    */
   private interface PlayerActon {
       /**
        * 跑
        */
       String running();

       /**
        * 跳
        */
       String jumping();

       /**
        * 投篮
        */
       String shooting();
   }

除此之外,看到下面的两个属性,分别带有一个注解:

@FieldDesc(type = "exclusive", value = " learn exclusive skills >> ") 
private String exclusive; 

@FieldDesc(value = "'s phone num? I don't know!") 
private String phone;

这里没什么别的含义,就是想在反射的时候,给这个属性赋默认值,在注解上面可以直接取值,比较方便。另外注解的属性还有一个type,这个type用来指定当前的属性是专属字段,因为不同的球员有不同的个性,我们通过这个类型判断下,如果是这个字段,那么要给上面的三个小学生赋不同的专属技能了:

   /**
    * 自定义注解,描述字段
    */
   @Documented
   @Target({ElementType.FIELD, ElementType.METHOD})
   @Retention(RetentionPolicy.RUNTIME)
   private @interface FieldDesc {
       /**
        * 类型
        */
       String type() default "";

       /**
        * 字段描述
        */
       String value() default "";
   }
复制代码

既然说到了技能了,那就把技能枚举定义一下:

    /**
    * 技能枚举
    */
   public static enum SkillsEnum {
       STAND_HAND("James", "STAND HAND!!!", "摊手"),
       SHAKE_HEAD("Curry", "SHAKE HEAD!!!", "摇头"),
       SHAKE_SHOULDERS("Durant", "SHAKE SHOULDERS!!!", "晃肩膀");
       private String studentName;
       private String skillsName;
       private String skillsNameDesc;

       SkillsEnum(String studentName, String skillsName, String skillsNameDesc) {
           this.studentName = studentName;
           this.skillsName = skillsName;
           this.skillsNameDesc = skillsNameDesc;
       }

       public String getStudentName() {
           return studentName;
       }

       public void setStudentName(String studentName) {
           this.studentName = studentName;
       }

       public String getSkillsName() {
           return skillsName;
       }

       public void setSkillsName(String skillsName) {
           this.skillsName = skillsName;
       }

       public String getSkillsNameDesc() {
           return skillsNameDesc;
       }

       public void setSkillsNameDesc(String skillsNameDesc) {
           this.skillsNameDesc = skillsNameDesc;
       }

       /**
        * 根据学生获取技能
        *
        * @return java.lang.String
        * @Param student
        * @Date 2022/12/15 14:21
        * @Author wjbgn
        **/
       public static String getSkillsByStudent(Student student) {
           for (SkillsEnum skillsEnum : SkillsEnum.values()) {
               if (skillsEnum.getStudentName().equals(student.getName())) {
                   return skillsEnum.getSkillsName();
               }
           }
           return null;
       }
   }

准备main方法

下面我们准备一个main方法,模拟一个场景:

    /***
    * 工具类测试样例
    *
    * @Param args
    * @return void
    * @Date 2022/12/15 15:10
    * @Author wjbgn
    **/
   public static void main(String[] args) {
       // 获取动作对应的结果,循环10次
       for (int i = 0; i < 10; i++) {
           try {
               // 随机获取一个学生
               Student student = studentList.get(new Random().nextInt(3));
               // 随机获取一个动作
               String action = actionList.get(new Random().nextInt(7));
               // 打印下随机结果
               System.out.println(getStudentField(action, student));

               // Thread.sleep(500L);
           } catch (Exception e) {
               throw new RuntimeException(e);
           }
       }
   }

如上所示,循环10次,分别调用getStudentField方法,方法后面会讲,这个方法就是为了获取学生的属性,但是我们从上面的代码看的出来,获取哪一个学生,获取学生的哪一个属性都是随机的,所以我们首先把这些属性和学生初始化一下,其中除了有字段属性,还有方法名称

 /**
    * 动作集合
    */
   private static List<Student> studentList = new ArrayList<>();

   /**
    * 动作集合
    */
   private static List<String> actionList = new ArrayList<>();

   /**
    * 初始化 动作集合,学生
    * 这里面都使用字段的名称,不使用get、set方法
    */
   static {
       // 获取球员的年龄
       actionList.add("age");
       // 获取球队
       actionList.add("team");
       // 获取电话
       actionList.add("phone");

       // 学习/使用专属动作
       actionList.add("exclusive");

       // 跑
       actionList.add("running");

       // 跳
       actionList.add("jumping");

       // 投篮
       actionList.add("shooting");

       studentList.add(new Student("James", " 37 years old", " From the Los Angeles Lakers", ""));
       studentList.add(new Student("Curry", " 34 years old", " From the Golden State Warriors", ""));
       studentList.add(new Student("Durant", " 33 years old", " From the Brooklyn Nets", ""));
   }

有了上面的初始化,我们就可以随机的调用getStudentField方法,步骤:

  • 首先将学生名字返回拼接到字符串
  • 通过属性名称学生对象调用前面封装好的ObjectDynamicUtil.getObjField方法
  • 如果没获取到属性,表示属性为空或者不是属性,是方法
    • 去调用setStudentField 方法,如果返回有值,则成功,再次ObjectDynamicUtil.getObjField获取一次
    • 如果仍然是空,那么就调用前面封装好的ObjectDynamicUtil.invokeMethod,按属性调用方法。
  • 返回结果
    /***
    * 动态获取学生属性
    *
    * @Param
    * @return void
    * @Date 2022/12/15 11:23
    * @Author wjbgn
    **/
   private static String getStudentField(String filedName, Student student) throws NoSuchFieldException {
       String msg = student.getName();
       // 获取属性值
       String value = ObjectDynamicUtil.getObjField(filedName, student);
       if (value == null || value == "") {
           // 如果获取属性是空怎么办?设置一个值进去
           setStudentField(filedName, student);
           // 设置值后,再次执行get方法
           value = ObjectDynamicUtil.getObjField(filedName, student);
       }
       // 调用学生实现的动作接口方法
       if (value == null) {
           value = (String) ObjectDynamicUtil.invokeMethod(filedName, student);
       }
       msg += value;
       return msg;
   }

下面看下设置学生属性值的方法:setStudentField,步骤:

  • 获取学生对象class
  • 根据属性名获取class的属性
  • 根据属性获取注解FieldDesc,即前面自定义的注解
  • 如果注解类型是exclusive,就根据学生从枚举类获取专属技能
  • 拼装结果并调用ObjectDynamicUtil.setObjField设置对象属性
    /***
    * 动态设置学生属性
    *
    * @Param
    * @return void
    * @Date 2022/12/15 11:23
    * @Author wjbgn
    **/
   private static void setStudentField(String filedName, Student student) throws NoSuchFieldException {
       Class<? extends Student> studentClass = student.getClass();
       Field declaredField = null;
       try {
           declaredField = studentClass.getDeclaredField(filedName);
       } catch (NoSuchFieldException e) {
           return;
       } catch (SecurityException e) {
           throw new RuntimeException(e);
       }
       FieldDesc annotation = declaredField.getAnnotation(FieldDesc.class);
       String value = annotation.value();
       String finalValue = value + (annotation.type().equals(filedName) ? SkillsEnum.getSkillsByStudent(student) : "");
       ObjectDynamicUtil.setObjField(filedName, finalValue, student);
   }

查看结果

到此为止,所有的代码都准备完毕了,记得把main方法的Thread。sleep注释放开,看到的结果更加直观。

此处注释是因为在码上掘金导致代码不能运行,不知道码上掘金是什么原因?

结果如下图所示:



如上所示,看到不同的学生可以做不同的事,展示不同的属性,都是随机动态获取的。

总结

反射是java中,最基础,也是最核心的内容,同样也是最有用的。然而实际的工作当中,我们接触到的机会少之又少,所以我们需要自我提升,将这些手段融会贯通。本文涉及的知识很小一部分反射知识,但是对应经常与表单,表格打交道的后端程序员来说,却非常有用,赶紧用起来吧~~

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码