5.7 反射
反射(reflection library)提供了动态操作java代码程序的方法,这项功能被大量应用于JavaBean中,使用反射,在设计或运行添加新类的时候,能够快速地应用开发工具动态查找新添加类的能力。
能够分析类能力的程序叫做反射(reflective)。
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("\n{\n");
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将所有的域设置为可访问的,对于每个域,获得了名字和值。