java的反射机制
当你要形成一个习惯的时候,第三篇往往是一个坎,事实上由于这个五一假期以及别的事情,我很乐意坑掉这周的,或者说推到下一周补上。现在看着好像还有机会完成?希望写完前言后真的有时间完成吧。
回:没按时完成。
java反射机制的核心是在程序运行过程中加载类并获取类的详细信息,从而操作类或者对象的属性方法,它并不需要事先(写代码的时候或者编译期)知道运行对象的是谁。其本质是JVM得到class对象后,再对class进行反编译获得类的各种信息。
java反射主要提供以下功能
-
在运行是判断任一对象所属的类
-
在运行时构造任一类的对象
-
在运行时判断任一个类所具有的成员方法和变量
-
在运行时调用任一对象的方法
java反射所涉及的类包括
-
java.lang.Class
-
java.lang.reflect.Constructor
-
java.lang.reflect.Field
-
java.lang.reflect.Method
-
java.lang.reflect.Modifier
-
java.lang.reflect.Array
反射机制的优缺点
-
优点:在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
-
缺点:a)反射会消耗一定的系统资源,所以不需要的时候没有必要使用动态加载 b)反射可以忽略访问限制词访问类的所有属性和方法,破坏了对象的封装性,会导致安全问题。
2、反射的主要用途
了解之前我以为之前没有接触过反射,但是其实,在我们使用部分IDE时,使用对象或者类调用它的属性方法时,一按点号就会列出对应的属性和方法,这里就用到了反射。
反射最主要的用途就是开发各种通用架构。很多框架(比如Spring)都是配置化的(如通过xml文件配置Bean),为了保证框架的通用性,它可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
譬如,在运用Struts 2框架的开发时我们一般会在struts.xml
里去配置Action
,比如:
<action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute"> <result>/shop/shop-index.jsp</result> <result name="error">login.jsp</result> </action>
配置文件与Action建立了一种映射关系,当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter
拦截,然后StrutsPrepareAndexecuteFliter
会去动态地创建Active实例。比如我们请求login.action
,那么StrutsPrepareAndExecuteFilter
会解析struts.xml
文件,检索action中name为login的Action,并根据class属性创建SimpleLoginAction
实例,并用invoke方法来调用execute方法,这个过程离不开反射。
抄抄拣拣都是这个例子,对于反射在通用框架中的使用也算有了个概念,不过下次遇到相似的配置文件,或许可以再了解一下。
对于框架开发人员来说,反射虽小作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。
3、反射的基本应用
3.1 获得Class对象
1 package liwx.learning; 2 3 public class reflectTest { 4 public static void main(String[] args){ 5 Class c1,c2,c3=null; 6 //方法一:类.class 7 c1 = F.class; 8 System.out.println(c1); 9 10 //方法二:类的对象.getClass() 11 //F f = new C();//若是F指针指向的时C的对象,返回的也是类C的Class:class liwx.learning.C 12 F f = new F(); 13 c2 = f.getClass(); 14 System.out.println(c2); 15 //方法三:使用Class类的forname的静态方法 16 try { 17 c3 = Class.forName("liwx.learning.F"); 18 System.out.println(c3); 19 }catch (Exception e){ 20 e.printStackTrace(); 21 } 22 System.out.println(c1==c2&&c2==c3); 23 } 24 25 } 26 class F{ 27 public F(){} 28 29 } 30 class C extends F{ 31 public C(){} 32 }
输出结果是:
1 class liwx.learning.F 2 class liwx.learning.F 3 class liwx.learning.F 4 true
以上三种方法得到的Class对象指向的是同一个位置,所以在“==”判断的时候返回true。第一个方法使用的是所有数据类型(包括基本数据类型)都有的“静态”属性class,第二种方法通过类的实例对象的getClass()方法,第三种方法是最常用的,使用的是Class类的静态方法forName。
到了这里,是否觉得第三种方法比较眼熟?
Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动
反射机制动态加载类的时候也会触发类加载过程,而类加载的初始化阶段会运行类的静态代码块,若是去看com.mysql.jdbc.Driver的源码可以发现它调用DriverManager的registerDriver方法,将mysql的driver对象注册到了DriverManager中。
1 public class Driver extends NonRegisteringDriver implements java.sql.Driver { 2 public Driver() throws SQLException { 3 } 4 5 static { 6 try { 7 //1. 新建一个mysql的driver对象 8 //2. 将这个对象注册到DriverManager中 9 DriverManager.registerDriver(new Driver()); 10 } catch (SQLException var1) { 11 throw new RuntimeException("Can't register driver!"); 12 } 13 } 14 }
可见,我们获取类的Class对象一方面是用来获取类的详细信息,方便接下来的分析和操作,同时,也可以通过动态加载类的过程去完成一些我们想要完成的初始化操作。
3.2 判断是否为某个类的实例
判断是否为某个类的实例有两种方法,一种是instanceof
关键字,一种是Class对象的isInstance()
方法,这个方法是一个native方法。
1 //方法一:instanceof关键字 2 boolean b1 = instance1 instanceof AClass_Name; 3 //方法二:Class对象的isInstance()方法 4 Class class1 = AClass.class; 5 boolean b2 = class1.isInstance(instance1);
例子:
1 /*判断是否为某个类的实例*/ 2 try { 3 F instant1 = new F(); 4 F instant2 = new C(); 5 C instant3 = new C(); 6 7 Class FClass =Class.forName("liwx.learning.F"); 8 Class CClass =Class.forName("liwx.learning.C"); 9 10 System.out.println("方法一:关键字instanceof判断instant1是否是类F的实例:"+(instant1 instanceof F)); 11 System.out.println("方法二:Class类的成员方法isInstance()判断instant1是否是类F的实例:"+FClass.isInstance(instant1)); 12 System.out.println("======================="); 13 System.out.println("父类指针所指向的子类对象是否属于子类的实例:"+CClass.isInstance(instant2)); 14 System.out.println("子类对象是否属于父类的实例:"+FClass.isInstance(instant3)); 15 }catch (Exception e){ 16 e.printStackTrace(); 17 }
1 方法一:关键字instanceof判断instant1是否是类F的实例:true 2 方法二:Class类的成员方法isInstance()判断instant1是否是类F的实例:true 3 ======================= 4 父类指针所指向的子类对象是否属于子类的实例:true 5 子类对象是否属于父类的实例:true
3.3 通过反射构造类的对象
通过反射构造类的对象也有两种方法
-
使用Class对象的
newInstance()
方法来创建Class对象对应类的实例。 -
先通过Class对象获取指定的Constructor对象,再调用Constructor对象的
newInstance()
方法来创建实例。这种方法可以用指定的构造器构造类的实例。
1 //方法一: 2 Class aclass = AClass.class; 3 Object a1 = aclass.newInstance(); 4 System.out.println(a1.getClass()); 5 //方法二: 6 Class aclass = AClass.class; 7 Constructor aconstructor = aclass.getConstructor(aclass); 8 Object a2 = aconstructor.newInstance(); 9 System.out.println(a2.getClass());
3.4 获取方法
获取类的方法集合的有三种方法,三种方法获得的结果也并不相同
-
getDeclaredMethods()
方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 -
getMethods()
方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。 -
getMethod(MethodName,param1.class,param2.class)
方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。 -
getDeclaredMethod(MethodName,param1.class,param2.class)
1 Class aclass = AClass.class; 2 Object a = aclass.newInstance(); 3 //getDeclaredMethods 4 Method[] declaredMethods = aclass.getDeclaredMethods(); 5 //getMethods 6 Method[] methods = aclass.getMethods(); 7 //getMethod 8 Method method = aclass.getMethod("methodname", 参数1数据类型.class, 参数2数据类型.class); 9 //getDeclaredMethod 10 Method method = aclass.getDeclaredMethod("methodname", 参数1数据类型.class, 参数2数据类型.class);
注:getDeclaredMethod
,getDeclaredMethods
可以访问定义在指定类中的全部方法,不受访问限制符限制,但不可访问继承自父类的方法,但是可以访问继承自父类并且在指定类中进行了重写的方法。
3.5 获取构造器
这个就与2.3构造类的对象的方法二相关,构造类的其中一个方法就是使用getConstructor()
获取类的构造器,接着使用构造器的newInstance()
方法来创建实例。
-
getConstructors()
:该方法只能获取当前Class所表示类的public修饰的构造器集合 -
getDeclaredConstructors()
:获取当前Class所表示类的所有的构造器集合,和访问权限无关 -
getConstructor(Class<?>... parameterTypes)
:获取当前Class所表示类中指定的一个public的构造器
需要注意的是,子类不会继承父类的构造函数,但可以通过super()来调用父类构造函数。所以这些函数返回的都是定义在各自的类之中的构造器,不会包含父类构造器。
3.6 获取成员变量
获取成员变量的方法有两个:
-
getField(FieldName)
:访问指定的公有的成员变量 -
getDeclaredField(FieldName)
:访问定义在该类中的指定成员变量,不受访问修饰符限制(包含public,protected,default,private),但无法访问继承自父类的成员变量 -
getFields()
:访问该类中全部的公有的成员变量,包含继承自父类的公有成员 -
getDeclaredFields()
:访问该类中定义的全部成员变量
注:当使用getField(FieldName)
来获取私有成员变量,或者使用getDeclaredField(FieldName)
来获取继承自父类的成员变量,或者使用两者访问不存在的成员变量名称,都是抛出NoSuchFieldException
3.7 调用方法
可以看到前面我们构造类的实例最后的结果都是赋给Object指针,Object类是所有类的父类,父类指针当然可以指向子类对象,但是父类指针只能调用父类中已有的方法。通过反射的方法构建的实例,我们可以通过getMethods()
获取类的方法Method,使用Method的invoke()
方法来进行调用。
例子(包含构造类的对象、获取方法、获取构造器、获取成员变量、调用方法):
1 class F{ 2 public static int val0 = 0; 3 public int val1 = 0; 4 public int val2 = 0; 5 private int pval = 1; 6 protected int ppval=2; 7 public F(){ 8 9 } 10 public F(int num){ 11 val1 = num; 12 val2 = num; 13 } 14 private F(int num1,int num2){ 15 val1 = num1; 16 val2 = num2; 17 } 18 public void show(){ 19 prishow(); 20 } 21 22 private void prishow(){ 23 System.out.println("val1,val2 = "+val1+","+val2 ); 24 } 25 26 27 } 28 class C extends F{ 29 public int val3 = 0; 30 private int pval2 = 1; 31 protected int ppvla2 = 2; 32 int ppval3 = 3; 33 public C(){ 34 //super(); 35 //System.out.println("children"); 36 } 37 public void show(){ 38 prishow2(); 39 } 40 public void show2(int num){ 41 val1 = num; 42 prishow2(); 43 } 44 45 private void prishow2(){ 46 System.out.println("chidren:val1,val2 = "+val1+","+val2 ); 47 } 48 }
1 /*通过反射构造类的实例,获取类的构造器、方法、变量并调用*/ 2 try { 3 //创建实例 4 System.out.println("****************************创建实例*******************************"); 5 Class fclass = F.class; 6 Class cclass = C.class; 7 System.out.println("获取类C的父类:"+cclass.getGenericSuperclass()); 8 Object f2 = fclass.newInstance();//直接使用Class对象构造类的实例,调用的是默认的构造器 9 System.out.println("使用class.newInstance构建的实例:"+f2.getClass());//class liwx.learning.F 10 System.out.println(); 11 12 //构造器 13 System.out.println("****************************构造器*******************************"); 14 Constructor[] fconstructors = fclass.getConstructors();//获取全部public修饰的构造器 15 System.out.println("类F使用getConstructors获取的构造器=============="); 16 for(Constructor c : fconstructors){ 17 System.out.println(c); 18 //public liwx.learning.F(int) 19 //public liwx.learning.F() 20 21 /*if(c.getParameterTypes().length==1){//我尝试使用getConstructors获得的构造器集合构造实例,事实上好像还不如使用getConstructor() 22 f2 = (F) c.newInstance(1); 23 ((F) f2).show();//val1,val2 = 1,1 24 }*/ 25 } 26 System.out.println(); 27 fconstructors = fclass.getDeclaredConstructors();//获取全部构造器,不限定访问权限 28 System.out.println("类F使用getConstructors获取的构造器=============="); 29 for(Constructor c : fconstructors){ 30 System.out.println(c); 31 //private liwx.learning.F(int,int) 32 //public liwx.learning.F(int) 33 //public liwx.learning.F() 34 } 35 System.out.println("类F使用getConstructor获取默认构造器=============="); 36 Constructor fconstructor = fclass.getConstructor();//获取默认无参的构造器 37 F f3 = (F) fconstructor.newInstance();//强制类型转换,调用类的函数时不需要使用invoke 38 f3.show();//val1,val2 = 0,0 39 System.out.println(); 40 41 System.out.println("类F使用getConstructor获取传入了一个int参数的构造器=============="); 42 fconstructor = fclass.getConstructor(int.class);//获取传入一个int类型参数的构造函数 43 f3 = (F) fconstructor.newInstance(1);//强制类型转换,调用类的函数时不需要使用invoke 44 f3.show();//val1,val2 = 1,1 45 System.out.println(); 46 47 48 System.out.println("类F使用getDeclaredConstructor获取双参数私有构造器=============="); 49 fconstructor = fclass.getDeclaredConstructor(int.class,int.class);//这是一个被private限制的构造器 50 fconstructor.setAccessible(true);//忽略访问限制符的限制 51 f3 = (F) fconstructor.newInstance(1,2);//强制类型转换,调用类的函数时不需要使用invoke 52 f3.show();//val1,val2 = 1,2 53 System.out.println(); 54 55 //成员变量 56 System.out.println("****************************成员变量*******************************"); 57 System.out.println("类F使用getField获取public修饰的成员变量=============="); 58 Field field = fclass.getField("val0");//获取特定名字的成员变量 59 System.out.println(field); 60 System.out.println(); 61 62 System.out.println("类F使用getDeclaredField获取私有成员变量=============="); 63 //field = fclass.getField("pval");//使用getField调用私有成员变量会报NoSuchFieldException 64 field = fclass.getDeclaredField("pval"); 65 System.out.println(field); 66 System.out.println(); 67 68 System.out.println("类C使用getFields获取全部公有成员变量,包含继承自类F的=============="); 69 Field[] fields = cclass.getFields(); 70 for(Field f:fields){ 71 System.out.println(f); 72 } 73 System.out.println(); 74 75 System.out.println("类C使用getDeclaredFields获取全部成员变量,不包含继承自类F的=============="); 76 fields = cclass.getDeclaredFields();//子类不会继承父类的私有成员变量 77 for(Field f:fields){ 78 System.out.println(f); 79 } 80 System.out.println(); 81 82 83 //获取方法、调用方法 84 System.out.println("****************************成员方法*******************************"); 85 System.out.println("类C使用getMethod获取public修饰的传入一个参数的方法show2=============="); 86 Method method = cclass.getMethod("show2", int.class); 87 System.out.println(method); 88 System.out.println("show2根据传入参数修改val1并打印vla1和val2,使用invoke调用"); 89 Object c = cclass.newInstance(); 90 method.invoke(c,22); 91 System.out.println(); 92 93 System.out.println("类C使用getMethod获取private修饰的无参数方法prishow2=============="); 94 method = cclass.getDeclaredMethod("prishow2"); 95 System.out.println(method); 96 System.out.println("使用invoke调用,私有方法需要先设置setAccessible才可以调用"); 97 c = cclass.newInstance(); 98 method.setAccessible(true);//私有方法需要先设置setAccessible才可以调用 99 method.invoke(c); 100 System.out.println(); 101 102 System.out.println("类C使用getMethods获取public修饰的全部方法=============="); 103 Method[] methods = cclass.getMethods(); 104 for(Method m:methods){ 105 System.out.println(m); 106 } 107 System.out.println(); 108 109 System.out.println("类C使用getDeclaredMethods获取定义在类C之中的全部方法=============="); 110 methods = cclass.getDeclaredMethods(); 111 for(Method m:methods){ 112 System.out.println(m); 113 } 114 System.out.println(); 115 116 }catch (InstantiationException e){ 117 e.printStackTrace(); 118 }catch (IllegalAccessException e){ 119 e.printStackTrace(); 120 }catch (NoSuchMethodException e){ 121 e.printStackTrace(); 122 }catch (InvocationTargetException e){ 123 e.printStackTrace(); 124 }catch (NoSuchFieldException e){ 125 e.printStackTrace(); 126 }
输出结果:
1 ****************************创建实例******************************* 2 获取类C的父类:class liwx.learning.F 3 使用class.newInstance构建的实例:class liwx.learning.F 4 5 ****************************构造器******************************* 6 类F使用getConstructors获取的构造器============== 7 public liwx.learning.F(int) 8 public liwx.learning.F() 9 10 类F使用getConstructors获取的构造器============== 11 private liwx.learning.F(int,int) 12 public liwx.learning.F(int) 13 public liwx.learning.F() 14 类F使用getConstructor获取默认构造器============== 15 val1,val2 = 0,0 16 17 类F使用getConstructor获取传入了一个int参数的构造器============== 18 val1,val2 = 1,1 19 20 类F使用getDeclaredConstructor获取双参数私有构造器============== 21 val1,val2 = 1,2 22 23 ****************************成员变量******************************* 24 类F使用getField获取public修饰的成员变量============== 25 public static int liwx.learning.F.val0 26 27 类F使用getDeclaredField获取私有成员变量============== 28 private int liwx.learning.F.pval 29 30 类C使用getFields获取全部公有成员变量,包含继承自类F的============== 31 public int liwx.learning.C.val3 32 public static int liwx.learning.F.val0 33 public int liwx.learning.F.val1 34 public int liwx.learning.F.val2 35 36 类C使用getDeclaredFields获取全部成员变量,不包含继承自类F的============== 37 public int liwx.learning.C.val3 38 private int liwx.learning.C.pval2 39 protected int liwx.learning.C.ppvla2 40 int liwx.learning.C.ppval3 41 42 ****************************成员方法******************************* 43 类C使用getMethod获取public修饰的传入一个参数的方法show2============== 44 public void liwx.learning.C.show2(int) 45 show2根据传入参数修改val1并打印vla1和val2,使用invoke调用 46 chidren:val1,val2 = 22,0 47 48 类C使用getMethod获取private修饰的无参数方法prishow2============== 49 private void liwx.learning.C.prishow2() 50 使用invoke调用,私有方法需要先设置setAccessible才可以调用 51 chidren:val1,val2 = 0,0 52 53 类C使用getMethods获取public修饰的全部方法============== 54 public void liwx.learning.C.show2(int) 55 public void liwx.learning.C.show() 56 public final void java.lang.Object.wait() throws java.lang.InterruptedException 57 public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException 58 public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException 59 public boolean java.lang.Object.equals(java.lang.Object) 60 public java.lang.String java.lang.Object.toString() 61 public native int java.lang.Object.hashCode() 62 public final native java.lang.Class java.lang.Object.getClass() 63 public final native void java.lang.Object.notify() 64 public final native void java.lang.Object.notifyAll() 65 66 类C使用getDeclaredMethods获取定义在类C之中的全部方法============== 67 public void liwx.learning.C.show2(int) 68 public void liwx.learning.C.show() 69 private void liwx.learning.C.prishow2()
3.8 利用反射创建数组
数组是java中比较特殊的一种类型,它涉及java.lang.reflect.Array
,可以赋给Object Reference。
1 /*使用反射创建数组*/ 2 Object array = Array.newInstance(String.class,10); 3 System.out.println(array.getClass()); 4 5 Array.set(array,0,"A"); 6 Array.set(array,1,"B"); 7 Array.set(array,2,"C"); 8 Array.set(array,3,"D"); 9 Array.set(array,4,"E"); 10 Array.set(array,5,"F"); 11 Array.set(array,6,"G"); 12 13 System.out.println(Array.get(array,3));//D 14 System.out.println(Array.get(array,7));//null
注:Array类中的newInstance()
中调用了native方法newArray()
,同时Array的set()
和get()
都是native方法。
4、总结
其实还有一些相关的譬如通过反射绕过泛型检查,以及invoke方法的详解,本来想一起写的,不过后面看看内容不少,而且我这次第三篇的确是没有按时完成,或许尝试用这个话题来混过惩罚。
参考