反射
反射的作用
在运行时,动态发现对象或类的真实信息,拥有的构造方法列表,普通方法列表,类属性,注解等相关信息来构造类的对象,调用相应的一些方法。为了解决这个问题,我们有两种做法:
- 第一种是假设在编译和运行时都完全知道类型的具体信息,这种情况下,我们可以直接先使用instanceof运算符进行判断,再利用强制类型转换成其运行时类型的变量即可。
- 第二种是编译时根本无法预知该对象和类可能属于那些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射。
获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。Java程序中获得Class对象通常有如下三种方式:
- 使用Class类的forName()静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名。(添加完整包名)。
try { Class clazz2 = Class.forName("java.lang.String"); } catch (ClassNotFoundException e) { e.printStackTrace(); }
2.调用某个类的class属性来获取该类对应的Class对象。例如Person.class将会返回Person类对应的Class对象。
Class clazz1 = String.class;
3.调用某个对象的getClass()方法,该方法是java.lang.Object类中的一个方法,该方法将会返回该对象所属类对应的Class对象。
String str = new String(); Class clazz3 = str.getClass();
从Class中获取信息
1.反射得到包含的构造器
2.反射得到包含的方法列表:
3.反射得到包含的属性列表:
4.反射得到包含的注解信息:
反射还可以得到包名等。
使用反射生成并操作对象
Class对象可以获得该类里包括的方法(由Method对象表示)、构造器(由Constructor对象表示)、Field(由Field对象表示),这三个类都定义在java.lang.reflect包下,并实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过COnstructor对象来调用对应的构造器创建对象,能通过Field对象直接访问并修改对象的属性值。
通过反射来生成对象如下两种方式:
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()方法时实际上是利用默认构造器来创建该类的实例。
// 使用无参构造器创建对象 可能会抛出异常 Class clazz4 = String.class; clazz4.newInstance();
2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInsance()方法来创建该Class对象对应类的实例,这种方式可以指定选择使用某个构造器来创建实例。
//使用指定的构造方法构造对象 Constructor<String> constructor = clazz4.getConstructor(String.class); String str2 = constructor.newInstance("Hello World");
通过反射来调用方法:
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods()方法或者getMethod()方法来获取全部方法或指定方法——这两个方法的返回值是Method对象数组,或者Method对象。每个Method对象对应一个方法,获得Method对象后,程序就可以通过该Method来调用对应的方法。在Method里包含以讹invoke方法,该方法的签名如下:
- Object invoke(Object obj ,Object... args) :该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。
代码:
Class clazz1 = String.class; Method method = clazz1.getMethod("substring", int.class); String str3 = "ABCDEFG"; String str6 = (String) method.invoke(str3, 3); System.out.println(str6);
当通过Method的invoke方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限,如果程序确实需要调用某个对象的private方法,可以先调用Method对象的setAccessible(boolean flag):将Method对象的accessible标志设置为指示的布尔值。值为true则表示该Method在使用时应该取消Java语言访问权限检查,值为false则反之。
通过反射访问属性值
通过Class对象的getFields()或getField()方法可以获取该类所包括的全部Field(属性)或指定Field。Field提供了如下两组方法来访问属性:
- getXxx(Object obj) : 获取obj对象该Field的属性值。此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消get后面的Xxxx,直接使用set(Object obj)
- setXxx(Object obj ,Xxxx val):将obj对象的该Field设置为val值。此处的Xxx对应8个基本类型,如果该属性的类型是引用类型则取消set后面的Xxx。
class Person { private String name; private int age; public String toString() { return "Person [ name:" + name + " , age:" + age + " ]"; } } public class FieldTest { public static void main(String[] args) throws Exception { //创建一个Person对象 Person p = new Person(); //获取Person类对应的Class对象 Class<Person> personClazz = Person.class; //获取Person类名为name的属性 //使用getDeclaredField,表明可获取各种访问控制符的field Field nameField = personClazz.getDeclaredField("name"); //设置通过反射访问该Field时取消访问权限检查 nameField.setAccessible(true); //调用set方法为p对象的指定Field设置值 nameField.set(p , "Yeeku.H.Lee"); //获取Person类名为age的属性 Field ageField = personClazz.getDeclaredField("age"); //设置通过反射访问该Field时取消访问权限检查 ageField.setAccessible(true); //调用setInt方法为p对象的指定Field设置值 ageField.setInt(p , 30); System.out.println(p); } }
反射操作数组
在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态创建数组,和操作数组元素等。
public class ArrayTest1 { public static void main(String args[]) { try { //创建一个元素类型为String ,长度为10的数组 Object arr = Array.newInstance(String.class, 10); //依次为arr数组中index为5、6的元素赋值 Array.set(arr, 5, "Struts2权威指南"); Array.set(arr, 6, "ROR敏捷开发最佳实践"); //依次取出arr数组中index为5、6的元素的值 Object book1 = Array.get(arr , 5); Object book2 = Array.get(arr , 6); //输出arr数组中index为5、6的元素 System.out.println(book1); System.out.println(book2); } catch (Throwable e) { System.err.println(e); } } }