反射实现了java代码对java代码的操作。
一、获得Class对象
有下列四种方式:
- 使用Class类的forName(String className) 静态方法,该静态方法需要传入类的全限定名称字符串。
- 调用某个类的class属性来获得该类对应的class对象。
- 调用某个对象的getClass()方法。
- 调用ClassLoader实例对象的loadClass方法。
由前两节对类加载的学习,可以了解到使用第二种和第三种方法获得Class对象,JVM已经将该类初始化,第三种方法由于创建了该类的实例,所以还会初始化该类的一个对象(及执行了非静态变量赋值+初始化代码块+构造函数)。
使用第一种方式,通过查看forName方法的源代码可以发现,默认会初始化类
上述方法调用了forName0的native方法:该方法对于是否初始化该类的参数 initialize 设置为true,并且传入了调用者(caller)的类加载器。也可以使用forName的重载方法设置initialize为false。
而使用第四种方式,loadClass方法源代码如下
这里的resolve参数代表“解析”,即类加载过程中第二步“连接”中的第三部分“解析”,默认情况不解析,也可以使用loadClass的重载方法将resolve设置为true。也就是说loadClass方法不会初始化该类。
二、从Class对象中获取信息
先来整理一下Class对象中通常获取的类信息:
Constructor<T> 这里的反省T代表构造器创建的对象类型。
Method
Field
<A extends Annotation> A ,由于使用@interface自定义注解时,都默认实现了Annotation接口,所以这里使用泛型A代表使用的
Class[] 内部类
Interfaces 该Class实现的全部接口
Superclass
Package 包
Modifiers 该类所有的修饰符
Name、SimpleName
TypeVariable<Class<T>>[] ,由getTypeParameters方法返回,该数组代表类的定义中包含的泛型类型声明,如果如果泛型声明的一般签名不符合Java虚拟机规范中指定的格式,则抛出GenericSignatureFormatError错误。
对于类的成员,又分为getEnclosing*、getDeclared*、getDeclaringClass、get*
它们的区别为:getEnclosing*返回一个Class、Method、Constructor对象,当前类定义在该Class、Method、Constructor中。
getDeclaringClass,返回一个类,当前类定义在这个类中作为成员(内部类或接口)。
getDeclared*,返回当前类中定义的*,与访问修饰符所限定的权限无关。
get*返回public的*。
上述信息都可以通过Class对象的getXXX方法来获得,具体方法的使用可以查阅文档。
除了getXXX方法,Class对象还提供了isXXX方法来帮助判断。
三、java8新增的方法参数反射
java8在java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor、Method两个字类。
Executable类提供了大量方法来获取修饰该方法或构造器的注解信息,还提供了isVarArgs()方法来判断该方法或构造器是否包含数量可变的形参,该类还提供了下面两个方法来获取该方法的参数个数和形参名
int getParameterCount();
Parameter[] getParameters。
其中Parameter类也是java8新增的类。通过Parameter类可以获取方法参数上面的信息,包括形参名、形参类型、泛型信息、注解信息等等。
由于javac在编译java文件时会忽略掉形参名,所以若想利用反射获取形参名,需要在编译时加入-parameters 参数,即可在编译时保留形参名信息。
获得Class的信息是在代码中动态操作Class的基础,下一节学习如何利用反射来动态操作代码。