类加载器
1、类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过:类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出现意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤称为:类加载或类的初始化。
类的加载
- 就是指将 class文件读入内存,并为之创建一个
java.lang.Class
对象; - 任何类被使用时,系统都会为之建立一个
java.lang.Class
对象。
类的连接
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;
- 准备阶段:负责为类的类变量分配内存,并设置默认初始值;
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用。
类的初始化
- 在该阶段,主要就是对类变量进行初始化
类的初始化步骤:
1. 假如类还未被加载和连接,则程序先加载并连接该类;
2. 假如该类的直接父类还未被初始化,则先初始化其直接父类;
3. 假如类中有初始化语句,则系统依次执行这些初始化语句。
注意:在执行第2步的时候,系统对直接父类的初始化步骤也遵循初始化步骤1~3
所以JVM最先初始化的总是Object类
类的初始化时机:
创建类的实例;
调用类的类方法(静态方法);
访问类或者接口的类变量,或者为该类变量赋值;
调用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象;
初始化某个类的子类;
直接调用 java.exe命令运行某个主类。
2、类加载器
类加载器的作用
作用:
负责将 .class 文件加载到内存,并为之生成对应的 java.lang.Class 对象
反射
1、反射概述
现有 Student和Teacher两个类
这两个类若想使用,首先就得通过类加载器加载对应的 .class 文件到内存,每一个 .class 文件中都应该包含:成员变量、构造方法、成员方法等信息。而每一个 .class 文件都包含这些信息,也就是说所有的 .class文件都有这样的信息,那么我们有没有一个类来描述这些信息呢?肯定是有的,这个类就是 Class类,所以这个类就是所有 .class对象所对应的类型。我们通过Class类里面的对象去使用这些成员变量、成员方法等,而不再通过 Student、Teacher去直接使用。
总结:
Java反射机制是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大地增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可以扩展。
2、开发步骤
1、创建 Student.java
package test;
/**
* @author Zeng Li
* @version 1.0
* @date 2022/3/2 0:30
*/
public class Student {
// 一个私有、一个默认、一个公共
private String name;
int age;
public String address;
public Student() {
}
private Student(String name) {
this.name = name;
}
Student(String name,int age){
this.name = name;
this.age = age;
}
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public void method1() {
System.out.println("method1()执行了...");
}
public void method2(String s) {
System.out.println("method2():" + s);
}
public String method3(String s, int i) {
return s + "," + i;
}
private void function() {
System.out.println("function()执行了...");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
2、获取Class类的对象
我们要想通过反射去使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象
@Test
public void test() throws ClassNotFoundException {
// 第一种方式获取Class对象
Class<Student> s1 = Student.class;
System.out.println(s1);
Class<Student> s2 = Student.class;
// 一个类在内存中只有一个字节码文件对象
System.out.println(s1 == s2); //true
// 第二种方式获取Class对象
Student student = new Student();
Class<? extends Student> s3 = student.getClass();
System.out.println(s1 == s3); //true
// 第三种方式获取Class对象
Class<?> s4 = Class.forName("test.Student"); // 推荐写法
System.out.println(s1 == s4); //true
}
3、反射获取构造方法
-
方法1
aClass.getConstructors(); // 返回一个包含 Constructor对象的数组,反映由该Class对象表示的类的所有 公共 构造函数
测试
@Test public void test1() throws ClassNotFoundException { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // 返回一个包含 Constructor对象的数组,反映由该Class对象表示的类的所有 公共 构造函数 Constructor<?>[] c1 = aClass.getConstructors(); for (Constructor<?> constructor : c1) { System.out.println(constructor); } }
-
方法2
aClass.getDeclaredConstructors() // 返回由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组
@Test public void test1() throws ClassNotFoundException { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // aClass.getDeclaredConstructors() // 返回由该 Class对象表示的类声明的所有构造函数的 Constructor对象的数组 Constructor<?>[] c1 = aClass.getDeclaredConstructors(); for (Constructor<?> constructor : c1) { System.out.println(constructor); } }
-
方法3
aClass.getConstructor() // 返回一个 Constructor对象,该对象反映由该Class对象表示的类的指定 公共 构造函数 // 参数:要获取的构造方法的参数的个数及数据类型对应的字节码文件对象 constructor.newInstance();
@Test public void test1() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // aClass.getConstructor() // 返回一个 Constructor对象,该对象反映由该Class对象表示的类的指定 公共 构造函数 // 参数:要获取的构造方法的参数的个数及数据类型对应的字节码文件对象 Constructor<?> constructor = aClass.getConstructor(); // 通过构造方法对象里面的对象来创建对象,这才叫反射 // 使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。 Object obj = constructor.newInstance();//获取无参构造函数 System.out.println(obj); }
-
测试:访问 公共 的带参构造方法
@Test public void test2() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // public Student(String name, int age, String address) { // 获取单个的构造方法 Constructor<?> constructor = aClass.getConstructor(String.class,int.class,String.class); // 通过构造方法对象里面的对象来创建对象,这才叫反射 // 使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。 Object obj = constructor.newInstance("rabbit",22,"上海"); System.out.println(obj); }
-
测试:访问 私有的带参的构造方法
@Test public void test3() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // public Student(String name, int age, String address) { // 获取单个的构造方法 Constructor<?> constructor = aClass.getDeclaredConstructor(String.class); // 我们不能通过私有的构造方法创建对象,但在反射中却可以,需要进行暴力反射 // 取消访问检查 constructor.setAccessible(true); Object obj = constructor.newInstance("rabbit"); System.out.println(obj); }
4、反射获取成员变量并使用
-
方式一:
Field[] getFields();
Field[] getFields(); //返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的所有可访问的 公共 的成员变量
@Test public void test4() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Field[] fields = aClass.getFields(); for (Field field : fields) { System.out.println(field); } }
-
方式二:
aClass.getDeclaredFields();
Field[] getDeclaredFields(); //返回一个包含Field对象的数组,Field对象反映由该Class对象表示的类或接口的所有成员变量
@Test public void test5() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { System.out.println(field); } }
-
方法三:为成员变量赋值
Field getField(String name); // 返回一个 Field对象,该对象反映由该Class对象表示的类或接口的指定 公共 成员字段
/** * Field getField(String name); * 返回一个 Field对象,该对象反映由该Class对象表示的类或接口的指定 公共 成员字段 * 步骤: * 1. 首先获取Class对象; * 2. 按照Class对象按照指定的成员变量得到成员变量对象; * 3. 最后通过成员变量对象调用set(obj1,obj2)方法 * 4. field.set(obj1,obj2):为obj1对象的成员变量field赋值为obj2 */ @Test public void test6() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Field addressField = aClass.getDeclaredField("address"); // 通过反射,为成员变量赋值:student.address = "上海" // 获取无参构造方法构造对象 Constructor<?> constructor = aClass.getConstructor(); Object obj = constructor.newInstance(); // 给obj的成员变量addressField赋值为上海 addressField.set(obj,"上海"); System.out.println(obj); }
-
练习
/** * 练习: * Student student = new Student(); * student.name = "rabbit" * student.age = 22 * student.address = "上海" */
@Test public void test7() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // 先获取无参构造方法 Constructor<?> constructor = aClass.getConstructor(); Object obj = constructor.newInstance(); Field nameField = aClass.getDeclaredField("name"); Field ageField = aClass.getDeclaredField("age"); Field addressField = aClass.getDeclaredField("address"); // 私有的成员变量,使用前也要暴力反射 nameField.setAccessible(true); ageField.setAccessible(true); nameField.set(obj,"rabbit"); ageField.set(obj,22); addressField.set(obj,"上海"); System.out.println(obj); }
5、反射获取成员方法并使用
-
测试一:获取所有公共方法,包括超类
Method[] getMethods(); 返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有 公共方法 包括由类或接口声明的对象以及从超类和超级接口继承的类
@Test public void test8() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Method[] methods = aClass.getMethods(); for (Method method : methods) { System.out.println(method); } }
-
测试二:获取所有方法,不包括超类
/** * Method[] getDeclaredMethods(); * 返回一个包含方法对象的数组,方法对象反映由该Class对象表示的类或接口的所有声明方法 * 包括:public\protected\default(package)访问和私有方法,但不包括继承方法 */
@Test public void test9() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Method[] methods = aClass.getDeclaredMethods(); for (Method method : methods) { System.out.println(method); } }
-
测试三:返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法
/** * Method getMethod(String name,Class<?>... parameterTypes ); * 返回一个 方法对象,该对象反映由该 Class对象表示的类或接口的指定公共成员方法 */
@Test public void test10() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); Method method = aClass.getMethod("method1"); // 获取无参构造方法创建对象调用方法 Constructor<?> constructor = aClass.getConstructor(); Object obj = constructor.newInstance(); // Object invoke(Object obj, Object... args) // 在具有指定参数的指定对象上调用此方法的对象表示的基础方法 // obj:调用方法的对象 // args:方法需要的参数 method.invoke(obj); }
-
练习4:通过反射实现如下操作
/** * 练习:通过反射实现如下操作 * Student student = new Student(); * student.method1(); * student.method2("rabbit"); * String str = student.method("rabbit",22); * System.out.println(str); * str.function(); */
@Test public void test12() throws Exception { // 获取Class对象 Class<?> aClass = Class.forName("test.Student"); // 获取无参构造方法创建对象调用方法 Constructor<?> constructor = aClass.getConstructor(); Object obj = constructor.newInstance(); // student.method1() Method method1 = aClass.getMethod("method1"); method1.invoke(obj); Method method2 = aClass.getMethod("method2", String.class); method2.invoke(obj,"rabbit"); Method method3 = aClass.getMethod("method3", String.class, int.class); Object o = method3.invoke(obj, "rabbit", 22); System.out.println(o); Method function = aClass.getDeclaredMethod("function"); function.setAccessible(true); function.invoke(obj); }
-
练习:通过反射越过泛型检查
/** * 练习:越过泛型检查 * 往 ArrayList<Integer> 中添加字符串对象 */
@Test public void test13() throws Exception { ArrayList<Integer> list = new ArrayList<>(); Class<? extends ArrayList> aClass = list.getClass(); Method method = aClass.getMethod("add", Object.class); method.invoke(list,"hello world"); System.out.println(list); }