类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载:将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
- 连接
- 验证:是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的静态成员分配内存,并设置默认初始化值
- 解析:将类的二进制数据中的符号引用替换为直接引用
- 初始化:执行类构造器
<clinit>()
方法的过程。
类初始化时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的
java.lang.Class
对象 - 初始化某个类的子类
- 直接使用
java.exe
命令来运行某个主类
类加载器
负责将.class
文件加载到内存中,并为之生成对应的Class对象
类加载器的组成
- Bootstrap ClassLoader 根类加载器
- 也被称为引导类加载器,负责Java核心类的加载,例如
System.String
等
- 也被称为引导类加载器,负责Java核心类的加载,例如
- Extension ClassLoader 扩展类加载器
- 负责JRE的扩展目录中jar包的加载
- System ClassLoader 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性。
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所有先要获取到每一个字节码文件对应的Class类型的对象。
为什么需要反射?
反射赋予了JVM动态编译的能力,动态编译可以最大限度的体现Java的灵活性,否则类的元信息只能通过静态编译的形式实现,而不能动态编译,也就是说在编译以后,程序在运行时的行为就是固定的了,如果要在运行时改变程序的行为,就需要动态编译,因此在Java中就需要反射来实现。
反射举例
首先实现一个Person
类,在Person
类中有成员变量,构造函数,和成员方法。
1 | package refTest; |
通过反射获取到字节码文件对象的三种方法
1 | Person p = new Person(); |
1 | Class c2 = Person.class() |
1 | Class c3 = Class.forName("refTest.Person"); |
通过反射获取公共有参构造方法并实例化
1 | package refTest; |
通过反射获取私有构造方法并实例化
1 | package refTest; |
通过反射获取所有的成员变量
1 | package refTest1; |
通过反射获取成员变量类型并使用
1 | package refTest1; |
通过反射获取所有的成员方法
1 | package refTest2; |
通过反射获取单个成员方法并使用
1 | package refTest2; |
几个反射的例子
通过反射运行配置文件内容
代码结构如下图
class.txt
中写的是配置文件
1 | className = test.Teacher |
Student
类
1 | package test; |
Teacher
类
1 | package test; |
Worker
类
1 | package test; |
Test
类实现
1 | package test; |
通过反射越过泛型检查
1 | package test; |
通过反射写一个通用的设置某个对象的某个属性为指定的值
首先写一个实现类Tool
1 | package test; |
测试类ToolDemo
1 | package test; |