反射
引入反射
情景:想通过读取配置文件来创建对象以及执行配置文件里该类的方法。
// 1. 使用Properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").tostring();
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + method);
// 2. 创建对象,传统的方法,行不通=》 反射机制
// 3.使用反射机制解决
//(1)加载类,返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2)通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
0bject o=cls.newInstance();
//(3)通过cls得到你加载的类 com.hspedu.Cat 的 methodName"hi" 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4)通过method1 调用方法:即通过方法对象来实现调用方法
method1.invoke(o);
反射机制
-
反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
-
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射
反射原理图
Class类
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,而是系统创建的
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类的完整结构,通过一系列API
- Class对象是存放在堆的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码变量名,方法名,访问权限等等)
类加载
静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
类加载时机
- 当创建对象时(new)----静态加载
- 当子类被加载时----静态加载
- 调用类中的静态成员时----静态加载
- 通过反射----动态加载
类加载流程图
加载阶段
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象
类的字节码二进制数据/元数据加载到方法区
连接阶段
连接阶段-验证
- 目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
- 可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
连接阶段-准备
- JVM 会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。这些变量所使用的内存都将在方法区中进行分配
连接阶段-解析
- 虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化
- 到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行
()方法的过程。 ()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。 - 虚拟机会保证一个类的
()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 ()方法,其他线程都需要阻塞等待,直到活动线程执行 ()方法完毕