1. 前言
· 本文章是用于 个人学习过程中 整理知识点的帖子,主题为:JavaSE 反射 入门
· 本文章出现的 遗漏、错误 欢迎点开这篇文章的各位指出。
· 本文章的知识大纲根据 韩顺平老师 JavaSE 教学视频 进行编写。
【感谢韩顺平老师带来的优质教学和对教育作出的贡献】
2. 反射的引入
● 场景模拟 ●
· 假设现在有一个类,其中包含一些方法,我们如果需要调用这些方法。我们会创建类对象,并调用方法。
· 当我们要调用类的其他方法,我们会 重新编写源代码 。但如果此时不允许我们动用源代码且调用相关方法时,我们应该怎么做?
· 此时就引出了 反射 的概念 。
● 基本特点 ●
· 反射是指:不动用源代码的基础上,扩展和使用类 ,只通过读取配置文件来实现类的调用 。
· 反射的最大特点就是,开闭模式:对类的访问是开放的、对类内部结构是关闭的 。
● 基本使用 ●
3. 反射原理
● Java程序 三大阶段 ●
· 在介绍反射机制前,我们先引出 Java 程序的三大阶段:编译阶段、加载阶段、运行阶段 。
- 编译阶段:编写类、类变量、方法、构造器 等一系列类成员对象。这些类会被 Javac 进行编译,从
Java 文件
变为class 字节码文件
。
- 加载阶段:类加载器
ClassLoader
会通过loadClass() 方法
将class 字节码文件
加载成 Class 类对象 并放到堆中,每一个 Class 类对象 会将各个成员对象封装到各自的集合中,例如:成员变量 放在Field 数组
、构造器 放在Constructor 数组
、成员方法 放在Method 数组
。。。
- 运行阶段:通过编写创建类对象、调用方法的代码后,在 堆 中 创建 类对象,此时这个类对象会指向类加载器 在 堆 中加载完毕的 Class 类对象 。此时两者产生了连通关系,即 通过创建类对象,获取相应的 Class 类对象,从而实现相应的操作。
● 图解 ●
图片来源:@韩顺平教育
● 反射机制 ●
· 反射机制就体现在 加载阶段,即类加载器加载 class 文件
创建 Class 类对象阶段 。
· 反射机制在运行阶段时可以实现:
1. 判断任意一个对象所属的类 。
2. 构造任意一个类对象 。
3. 得到任意一个类所具有的的成员变量和方法 。
4. 调用任意一个对象的成员变量和方法 。
5. 生成动态代理 。
· 反射的优点:可以动态的创建和使用对象【这也是框架底层的核心】,使用灵活 。
· 反射的缺点:使用反射基本是解释执行,对执行速度有一定影响 。
4. 相关类
● 基本介绍 ●
· java.lang.Class
:代表一个类,Class对象表示某个类加载后在堆中的对象
· java.lang.reflect.Method
:代表类的方法,Method对象表示某个类的方法
· java.lang.reflect.Field
:代表类的成员变量, Field对象表示某个类的成员变量
· java.lang.reflect.Constructor
:代表类的构造方法, Constructor对象表示构造器
· 根据 反射的基本使用 中 我们可以得出结论:
· Method、Field、Constructor 类 都是 基于 Class 类 调出的 类对象 ,但同时又可以独立出来执行更细致的方法 。
● 反射优化 ●
· 反射相较于传统的调用类,执行效率会明显降低,这是因为 反射的对象在使用时会执行访问检查 。
· Method、Field、Constructor 类对象 提供了 setAccessible()
方法 .
· 这个方法的作用是:启动和禁用访问安全检查的开关 【True
为 关闭 ,False
为开启 】
5. Class类
● 类结构体系 ●
● 基本介绍 ●
· Class 类对象 本身也是一个类,因此它也继承 Object 类 。
· Class 类对象是从系统中创建出来的 。
· 对于某一个类的 Class 类对象,在内存中只有一份,因为类只加载一次 。
· 每个实例类都会记得自己由哪一个 Class 对象实例所生 。
· Class 类对象可以完整的得到一个类的完整结构 。
· Class 类对象是存放在堆的 。
· 类的字节码二进制数据,是放在方法区的。【例如,方法代码、变量名、方法名、访问权限】
● 相关方法 ●
相关方法 | 解释 |
---|---|
forName ( String name ) | 返回指定类名name的Class对象 |
newlnstance ( ) | 调用缺省构造函数,获取该 Class对象 的一个实例 |
getPackage ( ) | 获得 包 |
getName ( ) | 返回对象各类成员的 名称 |
getlnterfaces ( ) | 获取当前 Class对象 的 接口 |
getClassLoader ( ) | 返回该类的 类加载器 |
getConstructors ( ) | 返回 Constructor 对象 数组 |
getField ( String name ) | 根据属性名返回 Field 对象 |
getFields ( ) | 返回 Field 对象 数组 |
● 获取方法 ●
· 获取 Class 类对象 的方式有六种:
1. 在已知全类名,且该类在类路径下时,可以使用 forName ( ) 静态方法 。
2. 若已知具体的类,则直接通过 class 方法 。【基本数据类型也可使用】
3. 在已知某个类的实例,直接调用 getClass ( ) 方法 。
4. 通过某个类的实例,先获取 Class 类对象,然后获取该对象对应的 类加载器:getClass ( ).getClassLoader ( ) 然后再调用 loadClass ( ) 方法获得类【该方法需传入类全名参数】
5. 若目标类是基本数据类型的包装类,可通过 TYPE 方法 获得 。
6. 类加载
● 基本介绍 ●
· 类加载分为两种:静态加载、动态加载 。
· 静态加载: 在编译时加载类对象,如果类不存在,则在编译期间报错【例:Dog dog = new Dog()
】
· 动态加载: 在运行时加载类对象,当执行到要加载相关类的时候,再进行加载操作【例:反射】
● 类加载时机 ●
· 创建对象时【例:new】
· 当子类被加载时,父类也加载 。
· 调用类中的静态成员时 。
· 通过反射 。
● 类加载过程 ●
● 类加载的三大阶段 详解 ●
-
加载阶段:
JVM 在该阶段的主要目的是 将字节码从不同的数据源转化为二进制字节流 ,并加载到内存中,同时生成一个代表该类的
java.lang.Class
对象 。【字节流会放在方法区中,而Class对象会放在堆中】
-
连接阶段:
a. 验证:包括文件格式(是否以魔数 oxcafebaba 开头)、元数据、字节码和符号引用验证 。
【目的是为了确保 Class 文件 的字节流包含的信息符合当前虚拟机的要求,且不会危害虚拟机自身的安全】
【可通过-Xerify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载时间】b. 准备:JVM 会在该阶段对静态变量分配内存并初始化,这些变量使用的内存都将在方法区中进行分配 。
c. 解析:是虚拟机将常量池内的符号引用替换为直接引用的过程 。
-
初始化阶段:
该阶段真正开始执行类中定义的 Java 程序代码,此阶段是执行
<clinit>()
方法的过程。a.
<clinit>()
方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有 静态变量 的赋值动作 和 静态代码块 中的语句,并进行合并。b.虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行完成 。