Java反射是一种非常强大的机制,它可以在同一个系统中去检测内部的类的字段、方法和构造函数。它非常多的Java框架中,都大量应用了反射技术,如Hibernate和Spring。可以说,反射机制的特征让Java可以构建异常强大,具备柔性的系统。
虽然Java反射机制存在效率低、速度慢和安全性不高等弊端,但在很多场景下,这些特征并不是主要的因素,或者可以通过缓存或者JVM优化等来逐步提升执行效率。
根据网站https://docs.oracle.com/javase/tutorial/reflect/index.html的说法,反射技术能够检查或修改在JVM中应用程序在运行时的行为,这是一个比较高级的语言特性和一种强大的技术,反射可以使应用程序实现本来不可能的操作。
下面对Java反射的基础知识进行说明和总结:
首先定义一个MyBase类,其中有私有字段,也有公有字段。同时也有公有方法和私有方法。MyBase类示例如下:
packagecom.hwdev.demo; /*** 基类示例* @author wangming*/publicclassMyBase { //公有字段 publicintversion=1; //私有字段privateStringdate="2021-05-18" ; //公有方法 publicvoidsay2(Stringmsg){ System.out.println("Hello "+msg); } //私有方法privateStringgetData(){ returnthis.date; } }
这里再定义一个Hello类,它继承自MyBase类,通过继承主要用于验证一下反射对于父类、子类的反射用法。
packagecom.hwdev.demo; /**** @author wangming*/publicclassHelloextendsMyBase { publicStringauthor="JackWang" ; publicintversion=1; privateStringcompany="kzcloud" ; publicvoidsay(Stringmsg){ System.out.println("Hello "+msg); } publicvoidsetAuthor(Stringauthor){ this.author=author; } publicStringgetAuthor(){ returnthis.author; } privateintgetVersion(){ returnthis.version; } }
关于Java反射,功能强大的就是可以通过字符串配置来动态从系统中调用方法或者修改其中某个对象的字段值,而Class.forName方法即可以通过传入类全路径字符串名称来获取对应的Class对象,非常的方便。另外通过getField方法和GetMethod方法可以获取指定字段和方法,并动态调用。
packagecom.hwdev.demo; importjava.lang.reflect.*; importjava.util.Arrays; /*** 反射第一种用法 Class.forName* @author wangming*/publicclassReflectDemo01 { publicstaticvoidTest() { try { //通过字符串全路径类名查找ClassClasshelloC=Class.forName("com.hwdev.demo.Hello"); //获取所有公有的字段数组,私有的无法获取 Field [] fields=helloC.getFields(); //打印字段数组内容System.out.println(Arrays.toString(fields)); //[public java.lang.String com.hwdev.demo.Hello.author, public int com.hwdev.demo.Hello.version]//实例化Objectobj=helloC.newInstance(); //获取特定字段,比遍历Field[]效率更高Fieldf=helloC.getField("author"); if (f!=null){ //关闭安全检查,提高效率f.setAccessible(true); //获取字段author内容Stringauthor= (String)f.get(obj); System.out.println("author="+author); //author=JackWang } //获取所有公有的方法数组,私有的无法获取 Method [] methods=helloC.getMethods(); //打印方法数组内容,子类等方法也可以获取到System.out.println(Arrays.toString(methods)); //本类所有方法Method [] methods2=helloC.getDeclaredMethods(); //打印方法数组内容System.out.println(Arrays.toString(methods2)); //获取特定方法,第二个参数String.class为say方法的参数类型//say(java.lang.String)Methodm=helloC.getDeclaredMethod("say",String.class); if (m!=null){ //关闭安全检查,提高效率m.setAccessible(true); //获取字段author内容ObjectreturnValue=m.invoke(obj, newObject[]{"Java"}); //Hello Javaif (returnValue!=null){ System.out.println("returnValue ="+returnValue); } } }catch(ClassNotFoundException|SecurityExceptionex){ ex.printStackTrace(); } catch(Exceptionex){ ex.printStackTrace(); } } }
JAVA编程不可不知的反射用法总结丨【奔跑吧!JAVA】
jackwangcumt发表于 2021/05/18 13:36:39
3.7k+00
【摘要】 Java反射是一种非常强大的机制,它可以在同一个系统中去检测内部的类的字段、方法和构造函数。它非常多的Java框架中,都大量应用了反射技术,如Hibernate和Spring。可以说,反射机制的特征让Java可以构建异常强大,具备柔性的系统。
Java反射是一种非常强大的机制,它可以在同一个系统中去检测内部的类的字段、方法和构造函数。它非常多的Java框架中,都大量应用了反射技术,如Hibernate和Spring。可以说,反射机制的特征让Java可以构建异常强大,具备柔性的系统。
虽然Java反射机制存在效率低、速度慢和安全性不高等弊端,但在很多场景下,这些特征并不是主要的因素,或者可以通过缓存或者JVM优化等来逐步提升执行效率。
根据网站https://docs.oracle.com/javase/tutorial/reflect/index.html的说法,反射技术能够检查或修改在JVM中应用程序在运行时的行为,这是一个比较高级的语言特性和一种强大的技术,反射可以使应用程序实现本来不可能的操作。
下面对Java反射的基础知识进行说明和总结:
首先定义一个MyBase类,其中有私有字段,也有公有字段。同时也有公有方法和私有方法。MyBase类示例如下:
package com.hwdev.demo; /** * 基类示例 * @author wangming */ public class MyBase { //公有字段 public int version = 1; //私有字段 private String date = "2021-05-18" ; //公有方法 public void say2(String msg){ System.out.println("Hello " + msg); } //私有方法 private String getData(){ return this.date; } }
这里再定义一个Hello类,它继承自MyBase类,通过继承主要用于验证一下反射对于父类、子类的反射用法。
package com.hwdev.demo; /** * * @author wangming */ public class Hello extends MyBase { public String author = "JackWang" ; public int version = 1; private String company = "kzcloud" ; public void say(String msg){ System.out.println("Hello " + msg); } public void setAuthor(String author){ this.author = author; } public String getAuthor(){ return this.author; } private int getVersion(){ return this.version; } }
关于Java反射,功能强大的就是可以通过字符串配置来动态从系统中调用方法或者修改其中某个对象的字段值,而Class.forName方法即可以通过传入类全路径字符串名称来获取对应的Class对象,非常的方便。另外通过getField方法和GetMethod方法可以获取指定字段和方法,并动态调用。
package com.hwdev.demo; import java.lang.reflect.*; import java.util.Arrays; /** * 反射第一种用法 Class.forName * @author wangming */ public class ReflectDemo01 { public static void Test() { try { //通过字符串全路径类名查找Class Class helloC = Class.forName("com.hwdev.demo.Hello"); //获取所有公有的字段数组,私有的无法获取 Field [] fields = helloC.getFields(); //打印字段数组内容 System.out.println(Arrays.toString(fields)); //[public java.lang.String com.hwdev.demo.Hello.author, public int com.hwdev.demo.Hello.version] //实例化 Object obj = helloC.newInstance(); //获取特定字段,比遍历Field[]效率更高 Field f = helloC.getField("author"); if (f != null){ //关闭安全检查,提高效率 f.setAccessible(true); //获取字段author内容 String author = (String)f.get(obj); System.out.println("author=" + author); //author=JackWang } //获取所有公有的方法数组,私有的无法获取 Method [] methods = helloC.getMethods(); //打印方法数组内容,子类等方法也可以获取到 System.out.println(Arrays.toString(methods)); //本类所有方法 Method [] methods2 = helloC.getDeclaredMethods(); //打印方法数组内容 System.out.println(Arrays.toString(methods2)); //获取特定方法,第二个参数String.class为say方法的参数类型 //say(java.lang.String) Method m = helloC.getDeclaredMethod("say",String.class); if (m != null){ //关闭安全检查,提高效率 m.setAccessible(true); //获取字段author内容 Object returnValue = m.invoke(obj, new Object[]{"Java"}); //Hello Java if (returnValue!=null){ System.out.println("returnValue =" + returnValue); } } }catch(ClassNotFoundException | SecurityException ex){ ex.printStackTrace(); } catch(Exception ex){ ex.printStackTrace(); } } }
这里需要注意:xxx.getMethods()方法默认情况下,会返回本类、父类、父接口的公有方法,而xxx.getDeclaredMethods()返回本类的 所有方法,包括私有的方法。同理,反射API中其他getXXX和getDeclaredXXX的用法类似。
packagecom.hwdev; importcom.hwdev.demo.ReflectDemo01; /*** * @author wangming*/publicclassMain { /*** @param args the command line arguments*/publicstaticvoidmain(String[] args) { //反射第一种用法 Class.forNameReflectDemo01.Test(); } }
执行程序,输出的结果如下:
[publicjava.lang.Stringcom.hwdev.demo.Hello.author, publicintcom.hwdev.demo.Hello.version, publicintcom.hwdev.demo.MyBase.version] author=JackWang[publicvoidcom.hwdev.demo.Hello.say(java.lang.String), publicvoidcom.hwdev.demo.Hello.setAuthor(java.lang.String), publicjava.lang.Stringcom.hwdev.demo.Hello.getAuthor(), publicvoidcom.hwdev.demo.MyBase.say2(java.lang.String), publicfinalvoidjava.lang.Object.wait() throwsjava.lang.InterruptedException, publicfinalvoidjava.lang.Object.wait(long,int) throwsjava.lang.InterruptedException, publicfinalnativevoidjava.lang.Object.wait(long) throwsjava.lang.InterruptedException, publicbooleanjava.lang.Object.equals(java.lang.Object), publicjava.lang.Stringjava.lang.Object.toString(), publicnativeintjava.lang.Object.hashCode(), publicfinalnativejava.lang.Classjava.lang.Object.getClass(), publicfinalnativevoidjava.lang.Object.notify(), publicfinalnativevoidjava.lang.Object.notifyAll()] [publicvoidcom.hwdev.demo.Hello.say(java.lang.String), publicvoidcom.hwdev.demo.Hello.setAuthor(java.lang.String), publicjava.lang.Stringcom.hwdev.demo.Hello.getAuthor(), privateintcom.hwdev.demo.Hello.getVersion()] HelloJava
从输出结果上来看,Field [] fields = helloC.getFields();不但可以获取Hello类的公有字段,还可以获取到父类MyBase的公有字段:com.hwdev.demo.MyBase.version
而Method [] methods2 = helloC.getDeclaredMethods();则可以获取本类,即Hello类所有的方法,包括公有的方法和私有的方法。因此,Java反射可以访问类的私有字段和方法,从而暴露内部的信息,这也是Java反射有安全问题的原因。
由于Java方法支持重载,因此同名的方法可以存在多个,即参数不同,因此在用反射调用方法时,需要指定方法的参数类型,这样就可以明确调到的具体是哪个方法签名,如Method m = helloC.getDeclaredMethod("say",String.class); 调用的是public void com.hwdev.demo.Hello.say(java.lang.String) 。
除了可以用Class.forName来进行反射外,还可以通过如下方式来获取反射对象:
Hellohello=newHello(); ClasshelloC=hello.getClass(); Field [] fields=helloC.getFields(); //////////////////////////////////////////ClasshelloC=Hello.class; Field [] fields=helloC.getFields();
下面介绍一下如何用Java反射修改私有字段和调用私有方法的示例:
packagecom.hwdev.demo; importjava.lang.reflect.*; /*** 反射访问私有字段和方法* @author wangming*/publicclassReflectDemo02 { publicstaticvoidTest() { try { //通过已有类查找Class ClasshelloC=Hello.class; //实例化Objectobj=helloC.newInstance(); //获取特定私有字段Fieldf=helloC.getDeclaredField("company"); if (f!=null){ //私有必须开启f.setAccessible(true); //设置私有字段值f.set(obj, "newKZ"); //获取字段author内容Stringfv= (String)f.get(obj); System.out.println("company="+fv); //company=newKZ } //获取私有方法Methodm=helloC.getDeclaredMethod("getVersion", null); if (m!=null){ //私有必须开启m.setAccessible(true); ObjectreturnValue=m.invoke(obj, null); if (returnValue!=null){ //returnValue =1System.out.println("returnValue ="+returnValue); } } }catch(SecurityExceptionex){ ex.printStackTrace(); } catch(Exceptionex){ ex.printStackTrace(); } } }
另外,Java反射可以获取注解信息,这个对于ORM框架来讲,用的非常多。
packagecom.hwdev.demo; importjava.lang.annotation.ElementType; importjava.lang.annotation.Retention; importjava.lang.annotation.RetentionPolicy; importjava.lang.annotation.Target; /*** 注解示例* @author wangming*/RetentionPolicy.RUNTIME) (ElementType.FIELD) (public@interfaceORMAnnotation { publicStringFieldName(); publicStringFieldType(); }
其中,@Retention(RetentionPolicy.RUNTIME)表示注解可以在运行时通过反射访问。@Target(ElementType.FIELD) 表示这个注解只能用在字段上面。同理,可以把FIELD改为Type或者Method等。
packagecom.hwdev.demo; importjava.lang.annotation.Annotation; importjava.lang.reflect.*; /*** 反射或者字段注解* @author wangming*/publicclassReflectDemo03 { publicstaticvoidTest() { try { ClasshelloC=Class.forName("com.hwdev.demo.HelloAnnotation"); Field[] fields=helloC.getDeclaredFields(); for(Fieldf : fields){ //关闭安全检查,提高效率f.setAccessible(true); Annotationann=f.getAnnotation(ORMAnnotation.class); if(anninstanceofORMAnnotation){ ORMAnnotationormAnn= (ORMAnnotation) ann; System.out.println("FieldName="+ormAnn.FieldName()); System.out.println("FieldType="+ormAnn.FieldType()); } } }catch(ClassNotFoundException|SecurityExceptionex){ ex.printStackTrace(); } catch(Exceptionex){ ex.printStackTrace(); } } }
执行此示例,则输出如下:
FieldName=f_authorFieldType=varchar(50) FieldName=f_verFieldType=int
再次,介绍一下如何用反射获取方法参数个数和类型,其中包含泛型的信息获取:
packagecom.hwdev.demo; importjava.util.ArrayList; importjava.util.List; /*** 泛型示例* @author wangming*/publicclassGenericCls { protectedList<String>myList=newArrayList(); publicGenericCls(intsize){ for(inti=0;i<size;i++){ myList.add("item"+i); } } publicList<String>getList(){ returnthis.myList; } publicStringgetList(intidx){ returnthis.myList.get(idx); } } packagecom.hwdev.demo; importjava.lang.reflect.*; /*** 反射获取方法参数* @author wangming*/publicclassReflectDemo05 { publicstaticvoidTest() { try { ClasshelloC=Class.forName("com.hwdev.demo.GenericCls"); //构造函数调用Objectobj=helloC.getConstructor(int.class).newInstance(3); Methodmethod=helloC.getMethod("getList", int.class); Class<?>returnType=method.getReturnType(); System.out.println("ReturnType = "+returnType.getName()); Parameter[] params=method.getParameters(); for(Parameterp : params){ System.out.println("ParameterizedType = "+p.getParameterizedType()); System.out.println("getModifiers = "+p.getModifiers()); System.out.println("getName = "+p.getName()); System.out.println("getType = "+p.getType()); } //调用方法Objectret=method.invoke(obj, newObject[]{2}); System.out.println("ret = "+ret.toString()); Methodmethod2=helloC.getMethod("getList", null); TypegreturnType=method2.getGenericReturnType(); System.out.println("getGenericReturnType = "+returnType.getName()); if(greturnTypeinstanceofParameterizedType){ ParameterizedTypetype= (ParameterizedType) greturnType; System.out.println("type = "+type.getTypeName()); Type[] typeArguments=type.getActualTypeArguments(); for(TypetypeArgument : typeArguments){ ClasstypeArgClass= (Class) typeArgument; System.out.println("typeArgClass = "+typeArgClass); } } }catch(ClassNotFoundException|SecurityExceptionex){ ex.printStackTrace(); } catch(Exceptionex){ ex.printStackTrace(); } } }
执行上述示例,输出如下所示。
ReturnType=java.lang.StringParameterizedType=intgetModifiers=0getName=arg0getType=intret=item2getGenericReturnType=java.lang.Stringtype=java.util.List<java.lang.String>typeArgClass=classjava.lang.String
关于反射还有非常多的知识点可以讲解,比如利用反射技术实现插件的动态加载等。反射的效率问题,可以通过使用高效的第三方反射库,或者加入缓冲机制来解决,这里不再赘述。