5.7 反射
反射(reflection library)提供了动态操作java代码程序的方法,这项功能被大量应用于JavaBean中,使用反射,在设计或运行添加新类的时候,能够快速地应用开发工具动态查找新添加类的能力。
能够分析类能力的程序叫做反射(reflective)。
- 在运行中分析类的能力;
- 在运行中查看对象;
- 实现通用的数组操作代码;
- 利用Method对象。
5.7.1 Class类
在程序运行期间,java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类,JVM利用运行时类型信息选择相应的方法执行。
可以通过专门的Java类访问这些信息,保存这些信息的类被称为Class,Object类中的getClass()方法将会返回一个Class类型的实例。
Employee e; ... Class cl = e.getClass();
如同用一个Employee对象表示一个特定的雇员属性一样,一个Class对象将表示一个特定类的属性。最常用的Class方法是getName。这个方法返回类的名字。
System.out.println(e.getClass().getName() + " " + e.getName());
如果e是一个雇员,会打印出:
Employee Harry Hacker
如果e是一个经理,会打印出:
Manager Harry Hacker
如果类在一个包里,包的名字也作为类名的一部分:
Date d = new Date(); Class cl = d.getClass(); String name = cl.getName();
也可以调用静态方法forName获得类名对应的Class对象。
String className = "java.util.Date"; Class cl = Class.forName(className);
如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。这个方法只有在className是类名或接口名的时候才能执行。否则,forName方法将会抛出一个checkedexception异常(已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(execption handler)。
获得Class类对象的第三种方法非常简单,如果T是任意的java类型,T.class将代表匹配的类对象。例如:
Class cl1 = Date.class; Class cl2 = int.class; Class cl3 = Double[].class;
一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类,例如,int不是类,但int.class是一个Class类型的对象。
JVM为每个类管理一个Class对象,因此,可以利用==运算符实现两个类对象比较的操作。例如:
if(e.getClass() == Employe.class) ...
还有一个很有用的方法newInstance(),可以用来快速地创建一个类的实例。例如,
e.getClass().newInstance();
创建一个与e具有相同类型的实例,newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。
将forName域newInstance配合起来使用,可以根据存储在字符串中的类名创建一个对象。
String s = "java.util.Date"; Object m = Class.forName(s).newInstance();
5.7.3 利用反射分析类的能力
java.lang.reflect中包含三个类Filed、Method和Constructor分别用于描述类的域、方法和构造器。
Filed类: getDeclaredFileds()
getName(返回项目的名称)、getType(返回域所属的类型)、getModifiers(返回一个整型数值,描述修饰符的情况)
Method类: getDeclaredMethods()
getName(返回项目的名称)、getModifiers(返回一个整型数值,描述修饰符的情况)、getReturnType(返回值类型)、 getParameterTypes(返回参数类型)
Constructors类: getDeclaredConstructors()
getName(返回项目的名称)、 getParameterTypes(返回参数类型)
package reflect_5_13; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Scanner; public class ReflectionTest { public static void main(String args[]) { String name; if(args.length > 0) { name = args[0]; } else { Scanner in = new Scanner(System.in); System.out.println("Enter class name(e.g.java.util.Date): "); name = in.next(); } try { //首先用cl保存获取的name.class Class cl = Class.forName(name); //获得超类supercl Class supercl = cl.getSuperclass(); //获取cl的修饰语(public/private/final...) String modifiers = Modifier.toString(cl.getModifiers()); if(modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print("class " + name); if(supercl != null && supercl != Object.class) System.out.print("extends " + supercl.getName()); System.out.print(" { "); printConstructors(cl); System.out.println(); printMethods(cl); System.out.println(); printFields(cl); System.out.println("}"); }catch(ClassNotFoundException e) { e.printStackTrace(); } System.exit(0); } /* * 输出所有的构造器 */ public static void printConstructors(Class cl) { Constructor[] constructors = cl.getDeclaredConstructors(); for(Constructor c:constructors) { //构造器的名字 String name = c.getName(); System.out.print(" "); //构造器的修饰符 String modifiers = Modifier.toString(c.getModifiers()); if(modifiers.length() > 0) { System.out.print(modifiers + " "); } System.out.print(name + "("); //构造器的参数类型 Class[] paramTypes = c.getParameterTypes(); for(int j = 0; j < paramTypes.length; j++) { if(j > 0) { System.out.print(", "); } System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /* * 输出所有的方法 */ public static void printMethods(Class cl) { Method[] methods = cl.getDeclaredMethods(); for(Method m:methods) { //方法的获取返回类型 Class retType = m.getReturnType(); //方法的名称 String name = m.getName(); System.out.print(" "); //方法的修饰符 String modifiers = Modifier.toString(m.getModifiers()); if(modifiers.length() > 0) { System.out.print(modifiers + " "); } System.out.print(retType.getName() + " " + name + "("); //将参数类型打印出来 Class[] paramTypes = m.getParameterTypes(); for(int j = 0; j < paramTypes.length; j++) { if(j > 0) { System.out.print(", "); } System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /* * 输出所有的域 */ public static void printFields(Class cl) { Field[] fields = cl.getDeclaredFields(); for(Field f:fields) { //域的类型 Class type = f.getType(); //域的名称 String name = f.getName(); System.out.print(" "); //域的修饰符 String modifiers = Modifier.toString(f.getModifiers()); if(modifiers.length() > 0) { System.out.print(modifiers + " "); System.out.println(type.getName() + " " + name + " "); } } } }
输出结果:
Enter class name(e.g.java.util.Date): java.util.Date public class java.util.Date { public java.util.Date(int, int, int, int, int, int); public java.util.Date(java.lang.String); public java.util.Date(); public java.util.Date(long); public java.util.Date(int, int, int); public java.util.Date(int, int, int, int, int); public boolean after(java.util.Date); public boolean before(java.util.Date); public boolean equals(java.lang.Object); public java.lang.String toString(); public int hashCode(); public java.lang.Object clone(); public int compareTo(java.util.Date); public volatile int compareTo(java.lang.Object); private void readObject(java.io.ObjectInputStream); private void writeObject(java.io.ObjectOutputStream); private final sun.util.calendar.BaseCalendar$Date normalize(); private final sun.util.calendar.BaseCalendar$Date normalize(sun.util.calendar.BaseCalendar$Date); public static long parse(java.lang.String); public static long UTC(int, int, int, int, int, int); public int getDate(); private static final java.lang.StringBuilder convertToAbbr(java.lang.StringBuilder, java.lang.String); private final sun.util.calendar.BaseCalendar$Date getCalendarDate(); private static final sun.util.calendar.BaseCalendar getCalendarSystem(long); private static final sun.util.calendar.BaseCalendar getCalendarSystem(int); private static final sun.util.calendar.BaseCalendar getCalendarSystem(sun.util.calendar.BaseCalendar$Date); public int getDay(); public int getHours(); private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar(); static final long getMillisOf(java.util.Date); public int getMinutes(); public int getMonth(); public int getSeconds(); private final long getTimeImpl(); public int getTimezoneOffset(); public int getYear(); public void setDate(int); public void setHours(int); public void setMinutes(int); public void setMonth(int); public void setSeconds(int); public void setYear(int); public java.lang.String toGMTString(); public java.time.Instant toInstant(); public java.lang.String toLocaleString(); public void setTime(long); public static java.util.Date from(java.time.Instant); public long getTime(); private static final sun.util.calendar.BaseCalendar gcal private static sun.util.calendar.BaseCalendar jcal private transient long fastTime private transient sun.util.calendar.BaseCalendar$Date cdate private static int defaultCenturyStart private static final long serialVersionUID private static final [Ljava.lang.String; wtb private static final [I ttb }
构造器:名称、修饰符、参数类型
方法:名称、修饰符、参数类型、返回类型
域:名称、类型
其他的API还需要详细的了解。
5.7.4 在运行时使用反射分析对象
查看任意对象的数据域名称和类型:
1、获得对于的Class对象;
2、通过Class对象调用getDeclaredFields。
在编写程序的时候,如果知道想要查看的域名和类型,查看指定的域是一件很容易的事情,而利用反射机制可以查看在编译时还不清楚的对象域。
查看对象域的关键方法是Field类中get方法。如果f是一个Field类型的对象(例如,通过getDeclaredFields得到的对象),obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj域的当前值。
Eployee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989); Class cl = harry.getClass(); Field f = cl.getDeclaredField("name"); Object v = f.get(harry);
实际上这段代码有问题,由于name是一个私有域,所以get方法将会抛出一个IllegalAccessException。只有利用get方法才能得到可访问域的值,除非拥有访问权限,否则Java安全机制只允许查看任意对象有那些域,而不允许读取它们的值。
反射机制的默认行为受限于java的访问控制,然而,如果一个java程序没有收到安全管理器的控制,就可以覆盖访问控制,为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。例如:
f.setAccessible(true);
setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调试、持久化存储和相似机制提供的。
get方法还有一个需要解决的问题,nama是一个String,因此它作为Object返回没有任何问题。但是,假设我们想要查看salary域,它属于double类型,而java中数值类型不是对象。要想解决这个问题,可以使用Fields类中的getDouble方法,也可以调用get方法,此时,反射机制将会自动将这个域值打包到对应的对象包装器中,这里将打包成Double。
调用f.get(obj, value)可以将obj对象的f域设置为新值。
使用getDeclaredFields获得所有的数据域,然后使用setAccessible将所有的域设置为可访问的,对于每个域,获得了名字和值。