反射&枚举
课程目标
1. 【理解】类加载器
2. 【理解】什么是反射
3. 【掌握】获取Class对象的三种方式
4. 【掌握】反射获取构造方法并创建对象
5. 【掌握】反射获取成员变量并使用
6. 【掌握】反射获取成员方法并使用
7. 【掌握】反射综合案例
8. 【理解】枚举
B友:https://www.bilibili.com/video/BV1QG4y1J76q/
VIP服务课程:https://edu.51cto.com/course/32767.html
类加载器
类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的【加载】,【连接】,【初始化】 这三个步骤来对类进行初始化。
如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化。
一个类的生命周期包括了 “加载”、“验证”、“准备”、“解析”、“初始化”、“使用”、“卸载” 这七个阶段, 我们只研究前五个阶段,这五个阶段又可以分为 “加载”、“连接、验证,解析” 、 “初始化”。
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
类的加载
类的加载就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象,用于加载二进制数据,类的加载主要做三件事情:
-
找到类文件(通过类的全限定名来获取定义此类的二进制字节流)
首先会根据各种途径(比如网络下载、数据库提取、从jar,zip中读取等)获取类的二进制数据
-
放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
把获取到的二进制数据读入内存,存储在运行时数据区的方法区,
这些二进制数据所代表的存储结构会被转化为方法区中运行时的数据结构
-
开个入口(生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口)
在方法区中创建相应的Class对象(这里的Class对象与平时所说的对象是不一样的,
当使用new创建实例对象时,就会通过Class对象在堆中创建实例对象)用来封装保存在方法区内的数据结构
类的连接
-
【验证阶段】:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
文件格式验证:验证字节流是否符合class文件的规范,确保输入的字节流能正确解析并存储到方法区
元数据验证:对字节码描述的信息进行语义分析,保证其描述的信息符合规范要求。
字节码验证:这个阶段是比较复杂的,通过数据流和控制流分析,对类的方法体进行校验,确保程序的合法性。符号引用验证:这里的符号引用不单单指类的,也包括方法。
发生在符号引用转为直接引用的时候,也就是解析阶段,
对常量池中各种符号引用的信息进行匹配性校验,确保解析动作正确执行 -
【准备阶段】:负责为类的类变量分配内存,并设置默认初始化值
需要注意的是:
* 静态变量只会给默认值。 * 例:public static int value = 123; // 此时赋给value的值是0,不是123。 * 静态常量(static final修饰的)则会直接赋值。 * 例:public static final int value = 123; // 此时赋给value的值是123。
-
【解析阶段】:将类的二进制数据中的符号引用替换为直接引用
类的初始化
类的初始化的主要工作是为静态变量赋程序设定的初值。
类的初始化步骤
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
- 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器
类加载器的作用
负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象。虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行!
简单来说:类加载器的作用,就是把class文件装进虚拟机
类加载机制
-
全盘负责
就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
-
父类委托
就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
-
缓存机制
保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
双亲委派机制
-
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
-
当前ClassLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载, 一直到bootstrp ClassLoader.
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
这样做的好处是什么?
1.避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2.为了安全。避免核心类,比如String被替换。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dntytLVb-1692620952897)(asseits/1de2c4d28014486fa71fdcd0938cd31a.png)]
Java中的内置类加载器
当JVM启动的时候,Java缺省开始使用如下三种类加载器
-
Bootstrap ClassLoader
引导类加载器(根类加载器), 用来加载 Java 的核心库,是用原生代码来实现的,
比如:System.String等,jre的lib下rt.jar文件中 -
Platform ClassLoader
平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
-
System ClassLoader
它也被称为应用程序类加载器(系统类加载器)(Application ClassLoader) ,与平台类加载器不同。 系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
负责在jvm启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar和类路径
-
UserClass Loader
自定义类加载器
如果核心包中没有项目所需要的jar,还有一个扩展加载器:将扩展的jar进行加载
类加载器的继承关系:System的父加载器为Platform,而Platform的父加载器为Bootstrap
反射
反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;对于这种动态的获取信息以及动态调用对象的方法的功能称为:java语言的反射机制。
反射理解:
可以通过这个对象拿到字节码文件,通过子节码文件还原到类的本身(也就是说你拿到类的Class对象去使用这个类的成员方法,成员变量,构造方法)
获取Class类对象的三种方式
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全限定类名)方法
public class Person {
//私有成员变量
private String name;
//默认成员变量
int age;
//公有成员变量
public String address;
//公有无参构造方法
public Person() {
}
//私有的有参构造方法 1个参数
private Person(String name) {
this.name = name;
}
//默认的有参构造方法 2个参数
Person(String name, int age) {
this.name = name;
this.age = age;
}
//公有的有参构造方法 3个参数
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
//公有的无参无返回值方法
public void show() {
System.out.println("show");
}
//公有的有参无返回值方法
public void method(String s) {
System.out.println("method " + s);
}
//公有的有参有返回值方法
public String getString(String s, int i) {
return s + "---" + i;
}
//私有的无参无返回值方法
private void function(