上一篇中讲了如何获取指定类的Class对象,及用Class对象获取该类的构造器、方法、成员变量,本篇主要探讨如何利用这些构造器、方法、成员变量等。
1.构造器,newInstance(Object ... initargs)方法
显而易见,利用构造器的目的就是产生一个对应类的实例:
public static void main(String[] args) { Class clazz = A.class; Constructor con = null; A a = null; try { con = clazz.getDeclaredConstructor(String.class, int.class); // con.setAccessible(true); a = (A) con.newInstance("张三", 19); } catch (Exception e) { e.printStackTrace(); } System.out.println(a); }
如上,在获取到Constructor对象con之后,可以调用其newInstance(Object ... initargs)方法获取到对应类的对象。值得注意的是,如果构造器A(String name, int age)是private的话,那么如果调用的不是getDeclaredConstructor()而是getConstructor()的话,则会报NoSuchMethodException没有此方法异常;调用getConstructor()得到Constructor对象,这个对象对应一个私有化的构造器,这时候如果直接调用newInstance(Object ... initargs)方法的话,会报IllegalAccessException非法访问权限异常的。解决办法是在调用newInstance(Object ... initargs)方法之前先执行setAccessible(true)。setAccessible(boolean flag)方法是Constructor类、Method类、Field类的公共父类AccessibleObject的方法,也就是说如果构造器、方法的访问权限是private时,要想通过反射调用这个方法,需要在调用前执行setAccessible(true),这样就不会报非法访问权限异常了。
但是,通常我们创建对应类对象的时候不会用Class对象获取完Constructor对象后再获取对应类实例,而是直接利用Class对象的newInstance()方法,通过这种方式生成对应类实例,其实是利用对应类的默认构造器,即无参构造器,所以对应类要有无参构造器(显示声明无参构造器或者根本就显示声明任何构造器),而且必须是public的,因为Class对象没有setAccessible()方法。
2.方法,public Object invoke(Object obj, Object... args):
invoke方法的调用者是一个Method对象,第一个参数是一个对应类的实例,后面参数是方法参数列表。可以看出,用invoke方法调用对应类某个方法的时候,就已经获得对应类的实例了,那为什么还要用invoke方法,而不是直接用对象调用呢?这是因为私有化的方法不能直接通过对象打点调用而只能通过invoke的方式调用:
public static void main(String[] args) { Class clazz = A.class; A a = null; Method method = null; try { method = clazz.getDeclaredMethod("say", String.class); a = (A) clazz.newInstance(); method.setAccessible(true); method.invoke(a, "zhoujielun"); } catch (Exception e) { e.printStackTrace(); } }
如上,Method对象在调用invoke方法之前先执行setAccessible(true)就可以执行对应类的私有化方法了。本例中创建对应类实例的方法就是直接Class对象.newInstance(),而没有通过产生构造器对象,然后利用构造器对象再获取对应类实例的方法。
3.成员变量,public Object get(Object obj);public void set(Object obj, Object value)
get方法的参数是一个对应类的实例;
set方法的第一个参数是一个对应类的实例,第二个参数是要给这个实例的某个成员变量赋的值。
一个Field对象其实就绑定一个成员变量,在生成Field对象时就绑定了,如Field field=clazz.getDeclaredField("name");,此时field就绑定了name成员变量。
public static void main(String[] args) { Class clazz = A.class; A a = null; Constructor con = null; Field field = null; try { con = clazz.getDeclaredConstructor(String.class, int.class); a = (A) con.newInstance("张三", 19); field = clazz.getDeclaredField("name"); String name = (String) field.get(a); System.out.println(name); field = clazz.getDeclaredField("age"); field.setAccessible(true); int age = field.getInt(a); System.out.println(age); } catch (Exception e) { e.printStackTrace(); } }
如上,如果在声明成员变量时时private的,则用Field对象操作成员变量之前要先执行setAccessible(true)。