文章目录
- 1、反射的基本概念
- 2、反射的作用
- 3、Class类
- 4、通过反射分析类
- 4.1 Constructor类
- 4.2 Field类
- 4.3 Method类
- 4.4 Modifier类
- 4.5 给定完整类名称,打印它的全部信息
- 5 在运行时使用反射分析对象
- 6 通过Method.invoke方法调用任意方法
1、反射的基本概念
反射库是一个提供了一系列丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序,能够分析类能力的程序称之为反射(reflection)。
通过反射能够将Java类中各个部分映射成为一个个Java对象,比如构造器、成员变量、方法。
2、反射的作用
Ⅰ. 反射最重要的用途就是开发各种通用框架,反射机制能够具有在运行时分析类的能力、在运行时查看对象。
Ⅱ. 反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Ⅲ. 当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为 Java 并不是动态语言,但是通过反射机制却可以做到这一点。
Ⅳ. 程序中一般的对象类型都是在编译期就确定下来的,而Java 反射机制可以动态的创建对象并调用其属性,这样对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象即使这个对象在编译期是未知的,
Ⅴ. 反射的核心:是 JVM 在运行时 才动态加载的类或调用方法或属性,他不需要事先(写代码的时候或编译期)知道运行对象是谁。
3、Class类
在程序运行期间,Java运行时系统始终未所有的对象维护一个运行时类型标识,它追踪每个对象所属的类,这些信息被保存在Class类的实例中。
以自定义的类为例说明Class类的功能:
Employee e
- 获得某个对象的的Class实例,并打印该对象所属的类的名称
Class cl = e.getClass();
System.out.println(cl.getName);
- 从类名字符串建立该类的Class实例
String className = "java.util.Random"
Class cl = Class.forName(className);
当className不是一个类名或接口名时,会抛出一个已检查异常ClassNotFoundException。
- 从类直接获取该类的Class实例(或基本类型)
Class cl1 = Employee.class
Class cl12 = int.class
一个Class实例实际上表示一种类型的标识,而这种类型可以是类,也可以是基本数据类型。实际上Class类是一个泛型类。
- 判断对象是否为某个类的实例
Employee e = new Employee();
Class cl = Employee.class;
cl.isInstance(e); //return true if e is instance of Employee
instanceof关键字也可。
- 动态创建一个类的实例
if(e.getClass() == Employee.class)
e.getClass().newInstance();
创建一个与e具有相同类类型的实例。newInstance方法调用默认构造器,如果类没有默认构造器,抛出异常。如果需要带参数动态创建类实例,可以用Constuctor类的newInstance方法。
4、通过反射分析类
java.lang.reflect包中有三个类Field、Method和Constructor分别用于描述类的域、方法和构造器。
通过Class实例中的方法可以获得对象所属类的域信息、方法信息和构造器信息:
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor<?>[] getConstructors()
Constructor<?>[] getDeclaredConstructors()
// ......
getDeclaredConstructor可以获取私有构造器的信息,但是如果想使用它,必须加上一步:
//c is a private constructor of a class
c.setAccessible(true);
同样,Class类中也有类似方法获取一个类的域和方法。
4.1 Constructor类
通过Class类获取某个类的构造器信息,用Constructor的实例来表示。
Employee e = new Employee();
Constructor[] constructors = e.getClass().getDeclaredConstructors();
通过Constructor可以查看构造器的详细信息:
for (Constructor c : constructors)
{
System.out.println(c.getName()) //构造器名
System.out.println(Modifier.toString(c.getModifiers)) //修饰符
System.out.println(c.getReturnType().getName()) //返回类型名
Class[] paramType = c.getParameterTypes() //参数类型名
}
通过Constructor的newInstance函数创建类实例
Object newInstance(Object[] args)
Construtor c; //c是一个只含String参数的Employee类构造器
Employee amy = (Employee)c.newInstance("Amy")
4.2 Field类
保存每个Field的修饰符、名称、类型名称等信息
4.3 Method类
保存每个Method的修饰符、返回类型、名称、参数列表类型等信息
invoke方法:
Method.invoke方法调用这个对象所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须时未包装的。
Object invoke(Object obj, Object... args)
详细使用见后面
4.4 Modifier类
Constructor、Field、Method类中都有一个getModifier()函数,而Modifier类保存的就是关于修饰符的信息,提供了一些静态方法和常量。
getModifier()返回的是一个整数,通过静态函数Modifier.toString可以将修饰符名称打印出来。
Constructor c;
String c_modifier_name = Modifier.toString(c.getModifier())
4.5 给定完整类名称,打印它的全部信息
Java核心技术卷1第10版 程序清单5-13:
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
{
//获取类名和超类名和类修饰符
Class cl = Class.forName(name);
Class supercl = cl.getSuperclass();
String modifiers = Modifier.toString(cl.getModifiers());
//打印类的修饰符 e.g public
if (modifiers.length() > 0 )
System.out.print(modifiers + "");
//打印类名 e.g class java.lang.Double
System.out.print("class " + name);
//如果有直接超类且不是Object,打印继承的超类名 e.g java.lang.Number
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();
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
/*
* 打印类的所有构造器
* @param cl 一个类
* */
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(");");
}
}
/*
* 打印类的所有方法
* @param cl 一个类
* */
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 + " " + 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(");");
}
}
/*
* 打印一个类的所有域
* @param cl 一个类
* */
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 + ";");
}
}
}
输入java.lang.Double,得到:
Enter class name (e.g. java.util.Date)
java.lang.Double
public finalclass java.lang.Double extends java.lang.Number
{
public java.lang.Double(double);
public java.lang.Double(java.lang.String);
public boolean equals(java.lang.Object);
public static class java.lang.String toString(double);
public class java.lang.String toString();
public int hashCode();
public static int hashCode(double);
public static double min(double, double);
public static double max(double, double);
public static native long doubleToRawLongBits(double);
public static long doubleToLongBits(double);
public static native double longBitsToDouble(long);
public volatile int compareTo(java.lang.Object);
public int compareTo(java.lang.Double);
public byte byteValue();
public short shortValue();
public int intValue();
public long longValue();
public float floatValue();
public double doubleValue();
public static class java.lang.Double valueOf(java.lang.String);
public static class java.lang.Double valueOf(double);
public static class java.lang.String toHexString(double);
public static int compare(double, double);
public static boolean isNaN(double);
public boolean isNaN();
public static boolean isFinite(double);
public static boolean isInfinite(double);
public boolean isInfinite();
public static double sum(double, double);
public static double parseDouble(java.lang.String);
public static final double POSITIVE_INFINITY;
public static final double NEGATIVE_INFINITY;
public static final double NaN;
public static final double MAX_VALUE;
public static final double MIN_NORMAL;
public static final double MIN_VALUE;
public static final int MAX_EXPONENT;
public static final int MIN_EXPONENT;
public static final int SIZE;
public static final int BYTES;
public static final java.lang.Class TYPE;
private final double value;
private static final long serialVersionUID;
}
Process finished with exit code 0
5 在运行时使用反射分析对象
利用反射机制可以查看在编译时还不清楚的对象域,比如通过Field中的get方法可以查看一个对象该域对应的值,当然,这种查看方式也要受限于Java的访问控制,若该域定义为private,就不能够直接查看。
那么如何访问private域/方法/构造器呢,通过调用Field、Method或Constructor对象的setAccessible方法。
setAccessible为AccessibleObject 类中的一个方法,是它是 Field、 Method 和 Constructor 类的公共超类。
//例如现在需要访问Employee类中的私有变量name
Employee harry = new Employee("Harry Hacker",35000,1989,10,1);
Class c1 = harry.getClass();
Field f = c1.getDeclaredField("name") //getField无法获得私有域
f.setAccessible(true);
//now ok to access haryy's name
String harry_name = (String) f.get(harry);
6 通过Method.invoke方法调用任意方法
C和C++中可以从函数指针执行任意函数,表面上看,Java没有提供方法指针,但是通过反射机制却可以调用任意方法。
首先介绍Method类中的invoke方法:
Object invoke(Object obj,Object ... args)
第一个参数是隐式参数,其余的对象args提供了显式参数。对于静态方法,隐式参数可以被忽略,即设置为null。
返回类型若是基本类型,则会返回其包装器类型。