目录
1 反射机制产生背景
详情见《IOC底层原理》
2 反射原理
class由JVM在执行过程中动态加载,加载完class之后,在堆内存的方法区中就产生了一个Class类型的对象,这个对象就包含了完整的类的结构信息。因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。这种通过Class
实例获取class
信息的方法称为反射(Reflection)。
2.1 理解Class类
- Class本身也是一个类。
- Class对象只能由系统建立对象。
- 一个加载的类在 JVM 中只会有一个Class实例。
- 一个Class对象对应的是一个加载到JVM中的一个.class文件。
- 每个类的实例都会记得自己是由哪个Class实例所生成。
- 通过Class可以完整地得到一个类中的所有被加载的结构。
- Class类是反射的根源,任何想动态加载、运行的类,唯有先获得相应的Class实例。
2.2 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class对象,作为方法区中类数据的访问入口。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范。
准备:正式为类变量分配内存并设置类变量默认初始值的阶段。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化:执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器),初始化过程如下:
- 虚拟机启动,先初始化main方法所在的类。
- new一个类的对象。
- 调用类的静态成员(除了final常量)和静态方法。
- 使用java.lang.reflect包的方法对类进行反射调用。
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类。
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
第二步:链接结束后m=0 (为类变量分配内存并设置类变量默认初始值)
第三步:初始化后,m的值由<clinit>()方法执行决定,这个A的类构造器<clinit>()方法由
类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于:
<clinit>(){
m = 300;
m = 100;
}
3 反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注
- 生成动态代理
4 获取Class实例
方法一:直接通过一个class
的静态变量class
获取,若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。
Class cls = String.class;
Class cls2 = int.class;
Class cls3 = Double[].class;
方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()
方法获取。
String s = "Hello";
Class cls = s.getClass();
方法三:如果知道一个class
的完整类名,可以通过静态方法Class.forName()
获取,可能抛出ClassNotFoundException
Class cls = Class.forName("java.lang.String");
5 创建运行时类的对象
5.1 Class.newInstance()
通过newInstance()动态的创建一个类的实例,(要求:1.类必须有一个无参数的默认构造器。2.类的构造器的访问权限需要足够。),调用默认的构造器初始化新创建的对象,如果类没有默认的构造器则会报错。Class.newInstance() 要求被调用的构造函数是可见的,即必须是public类型的。
5.2 Constructor.newInstance()
Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。 Constructor.newInstance() 在特定的情况下,可以调用私有的构造函数。
String className = "A";
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
//1.根据全类名获取对应的Class对象
String name = “atguigu.java.Person";
clazz = Class.forName(name);
//2.调用指定参数结构的构造器,生成Constructor的实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
//3.通过Constructor的实例创建对应类的对象,并初始化类属性
Person p2 = (Person) con.newInstance("Peter",20);
System.out.println(p2);
6 获取运行时类的完整结构
6.1 实现的全部接口
public Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
6.2 所继承的父类
public Class <? Super T>getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。
6.3 全部的构造器
public Constructor<T>[] getConstructors()。
返回此 Class 对象所表示的类的所有public构造方法。
public Constructor<T>[] getDeclaredConstructors()。
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
取得修饰符: public int getModifiers();
取得方法名称: public String getName();
取得参数的类型:public Class<?>[] getParameterTypes();
6.4 全部的方法
public Method[] getDeclaredMethods()。
返回此Class对象所表示的类或接口的全部方法。
public Method[] getMethods()。
返回此Class对象所表示的类或接口的public的方法。
Method类中:
public Class<?> getReturnType()取得全部的返回值。
public Class<?>[] getParameterTypes()取得全部的参数。
public int getModifiers()取得修饰符。
public Class<?>[] getExceptionTypes()取得异常信息。
7 调用运行时类的指定结构
7.1 调用指定方法
1. 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
2. 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
说明:
Object 对应原方法的返回值,若原方法无返回值,此时返回null
若原方法若为静态方法,此时形参Object obj可为null
若原方法形参列表为空,则Object[] args为null
若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
7.2 调用指定属性
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
在Field中:
public Object get(Object obj) 取得指定对象obj上此Field的属性内容
public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容。