背景 相信大家在日常的工作中,一定遇到过以下的某个场景:前端需要选择某些字段,去展示不同字段下的信息,如果在字段和方法没有绑定的情况下,如何调用get方法呢?如果有一张横转纵的表,存储的do都是实际的字段名称,那么如何转化成实体类对应的get、set方法去执行操作呢?如果我想根据方法名称去调用对象的某个方法呢? 相信有些基础的程序员会立刻想到使用反射就好了,没错,就是这么简单,但是每用到一次,咱们就去写一次也是比较麻烦的,所以我们可以将它封装成工具类,用的时候直接去调用就好了。实现 目前我在工具类实现了三个方法,分别是:根据属性名调用对象get方法根据属性名调用对象的set方法根据方法名调用方法根据属性名调用对象get方法 实现逻辑:获取对象的class获取对象的所有属性获取对象的声明方法遍历属性匹配方法invoke执行该方法返回方法的返回值根据属性名获取属性值returnjava。lang。StringParamfiledNameParamobjDate2022121515:06AuthorwjbgnpublicstaticStringgetObjField(StringfiledName,Objectobj){AtomicReferenceStringvaluenewAtomicReference();Classlt;?aClassobj。getClass();获取所有属性Field〔〕declaredFieldsaClass。getDeclaredFields();获取所有方法Method〔〕declaredMethodsaClass。getDeclaredMethods();Arrays。stream(declaredFields)。forEach(filed{if(filed。getName()。equals(filedName)){属性存在,尝试获取属性,调用get方法,此处get方法需要组装Arrays。stream(declaredMethods)。forEach(method{if(method。getName()。toLowerCase()。equals(getfiledName)){try{执行方法Objectinvokemethod。invoke(obj);value。set(invokenull?null:invoke。toString());}catch(Exceptione){thrownewRuntimeException(e);}}});}});returnvalue。get();}根据属性名调用对象的set方法 实现逻辑:获取对象的声明方法遍历方法匹配方法名称invoke执行该方法根据属性名设置属性值returnvoidParamfiledName字段名Paramvalue字段值Paramobj对象Date2022121514:41AuthorwjbgnpublicstaticvoidsetObjField(StringfiledName,Stringvalue,Objectobj){Classlt;?aClassobj。getClass();Arrays。stream(aClass。getDeclaredMethods())。forEach(method{if(method。getName()。toLowerCase()。equals(setfiledName)){try{method。invoke(obj,value);}catch(IllegalAccessExceptione){thrownewRuntimeException(e);}catch(InvocationTargetExceptione){thrownewRuntimeException(e);}}});}根据方法名调用方法 实现逻辑:获取对象的所有方法不同于获取get、set,此处需要获取所有的方法,包括实现和继承来的。遍历方法匹配方法名称invoke执行该方法返回方法返回值根据方法名调用该方法returnjava。lang。ObjectParamfiledNameParamobjDate2022121515:05AuthorwjbgnpublicstaticObjectinvokeMethod(StringfiledName,Objectobj){AtomicReferenceObjectresultnewAtomicReference();Arrays。stream(obj。getClass()。getMethods())。forEach(method{if(method。getName()。toLowerCase()。contains(filedName)){有方法包含该属性try{Objectinvokemethod。invoke(obj);result。set(invoke);return;}catch(Exceptione){thrownewRuntimeException(e);}}});returnresult。get();}测试准备基础代码 使用一段代码来测试下我们的方法,首先准备一些基础类。 背景是有三个小学生,分别是詹姆斯,库里,杜兰特,每个人共有一些属性,如下所示:学生类,每个学生可以跑,跳,投篮staticclassStudentimplementsPlayerActon{privateStringname;privateStringage;privateStringteam;FieldDesc(typeexclusive,valuelearnexclusiveskills)privateStringexclusive;FieldDesc(valuesphonenum?Idontknow!)privateStringphone;publicStudent(Stringname,Stringage,Stringteam,Stringphone){this。namename;this。ageage;this。teamteam;this。phonephone;}publicStudent(){}publicStringgetExclusive(){returnexclusive;}publicvoidsetExclusive(Stringexclusive){this。exclusiveexclusive;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this。namename;}publicStringgetAge(){returnage;}publicvoidsetAge(Stringage){this。ageage;}publicStringgetTeam(){returnteam;}publicvoidsetTeam(Stringteam){this。teamteam;}publicStringgetPhone(){returnphone;}publicvoidsetPhone(Stringphone){this。phonephone;}OverridepublicStringrunning(){returnisrunning!;}OverridepublicStringjumping(){returnisjumping!;}OverridepublicStringshooting(){returnmakeashot!;}} 上面的实体类实现了一个接口PlayerActon,里面是三个方法,表示运动员可以跑,跳,投篮:动作接口privateinterfacePlayerActon{跑Stringrunning();跳Stringjumping();投篮Stringshooting();} 除此之外,看到下面的两个属性,分别带有一个注解:FieldDesc(typeexclusive,valuelearnexclusiveskills)privateStringexclusive;FieldDesc(valuesphonenum?Idontknow!)privateStringphone; 这里没什么别的含义,就是想在反射的时候,给这个属性赋默认值,在注解上面可以直接取值,比较方便。另外注解的属性还有一个type,这个type用来指定当前的属性是专属字段,因为不同的球员有不同的个性,我们通过这个类型判断下,如果是这个字段,那么要给上面的三个小学生赋不同的专属技能了:自定义注解,描述字段DocumentedTarget({ElementType。FIELD,ElementType。METHOD})Retention(RetentionPolicy。RUNTIME)privateinterfaceFieldDesc{类型Stringtype()default;字段描述Stringvalue()default;}复制代码 既然说到了技能了,那就把技能枚举定义一下:技能枚举publicstaticenumSkillsEnum{STANDHAND(James,STANDHAND!!!,摊手),SHAKEHEAD(Curry,SHAKEHEAD!!!,摇头),SHAKESHOULDERS(Durant,SHAKESHOULDERS!!!,晃肩膀);privateStringstudentName;privateStringskillsName;privateStringskillsNameDesc;SkillsEnum(StringstudentName,StringskillsName,StringskillsNameDesc){this。studentNamestudentName;this。skillsNameskillsName;this。skillsNameDescskillsNameDesc;}publicStringgetStudentName(){returnstudentName;}publicvoidsetStudentName(StringstudentName){this。studentNamestudentName;}publicStringgetSkillsName(){returnskillsName;}publicvoidsetSkillsName(StringskillsName){this。skillsNameskillsName;}publicStringgetSkillsNameDesc(){returnskillsNameDesc;}publicvoidsetSkillsNameDesc(StringskillsNameDesc){this。skillsNameDescskillsNameDesc;}根据学生获取技能returnjava。lang。StringParamstudentDate2022121514:21AuthorwjbgnpublicstaticStringgetSkillsByStudent(Studentstudent){for(SkillsEnumskillsEnum:SkillsEnum。values()){if(skillsEnum。getStudentName()。equals(student。getName())){returnskillsEnum。getSkillsName();}}returnnull;}}准备main方法 下面我们准备一个main方法,模拟一个场景:工具类测试样例ParamargsreturnvoidDate2022121515:10Authorwjbgnpublicstaticvoidmain(String〔〕args){获取动作对应的结果,循环10次for(inti0;i10;i){try{随机获取一个学生StudentstudentstudentList。get(newRandom()。nextInt(3));随机获取一个动作StringactionactionList。get(newRandom()。nextInt(7));打印下随机结果System。out。println(getStudentField(action,student));Thread。sleep(500L);}catch(Exceptione){thrownewRuntimeException(e);}}} 如上所示,循环10次,分别调用getStudentField方法,方法后面会讲,这个方法就是为了获取学生的属性,但是我们从上面的代码看的出来,获取哪一个学生,获取学生的哪一个属性都是随机的,所以我们首先把这些属性和学生初始化一下,其中除了有字段属性,还有方法名称:动作集合privatestaticListStudentstudentListnewArrayList();动作集合privatestaticListStringactionListnewArrayList();初始化动作集合,学生这里面都使用字段的名称,不使用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(newStudent(James,37yearsold,FromtheLosAngelesLakers,));studentList。add(newStudent(Curry,34yearsold,FromtheGoldenStateWarriors,));studentList。add(newStudent(Durant,33yearsold,FromtheBrooklynNets,));} 有了上面的初始化,我们就可以随机的调用getStudentField方法,步骤:首先将学生名字返回拼接到字符串通过属性名称和学生对象调用前面封装好的ObjectDynamicUtil。getObjField方法如果没获取到属性,表示属性为空或者不是属性,是方法去调用setStudentField方法,如果返回有值,则成功,再次ObjectDynamicUtil。getObjField获取一次如果仍然是空,那么就调用前面封装好的ObjectDynamicUtil。invokeMethod,按属性调用方法。返回结果动态获取学生属性ParamreturnvoidDate2022121511:23AuthorwjbgnprivatestaticStringgetStudentField(StringfiledName,Studentstudent)throwsNoSuchFieldException{Stringmsgstudent。getName();获取属性值StringvalueObjectDynamicUtil。getObjField(filedName,student);if(valuenullvalue){如果获取属性是空怎么办?设置一个值进去setStudentField(filedName,student);设置值后,再次执行get方法valueObjectDynamicUtil。getObjField(filedName,student);}调用学生实现的动作接口方法if(valuenull){value(String)ObjectDynamicUtil。invokeMethod(filedName,student);}msgvalue;returnmsg;} 下面看下设置学生属性值的方法:setStudentField,步骤:获取学生对象class根据属性名获取class的属性根据属性获取注解FieldDesc,即前面自定义的注解如果注解类型是exclusive,就根据学生从枚举类获取专属技能拼装结果并调用ObjectDynamicUtil。setObjField设置对象属性动态设置学生属性ParamreturnvoidDate2022121511:23AuthorwjbgnprivatestaticvoidsetStudentField(StringfiledName,Studentstudent)throwsNoSuchFieldException{Classlt;?extendsStudentstudentClassstudent。getClass();FielddeclaredFieldnull;try{declaredFieldstudentClass。getDeclaredField(filedName);}catch(NoSuchFieldExceptione){return;}catch(SecurityExceptione){thrownewRuntimeException(e);}FieldDescannotationdeclaredField。getAnnotation(FieldDesc。class);Stringvalueannotation。value();StringfinalValuevalue(annotation。type()。equals(filedName)?SkillsEnum。getSkillsByStudent(student):);ObjectDynamicUtil。setObjField(filedName,finalValue,student);}查看结果 到此为止,所有的代码都准备完毕了,记得把main方法的Thread。sleep注释放开,看到的结果更加直观。 此处注释是因为在码上掘金导致代码不能运行,不知道码上掘金是什么原因? 结果如下图所示: 如上所示,看到不同的学生可以做不同的事,展示不同的属性,都是随机动态获取的。总结 反射是java中,最基础,也是最核心的内容,同样也是最有用的。然而实际的工作当中,我们接触到的机会少之又少,所以我们需要自我提升,将这些手段融会贯通。本文涉及的知识很小一部分反射知识,但是对应经常与表单,表格打交道的后端程序员来说,却非常有用,赶紧用起来吧