当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过:类的加载、类的连接、类的初始化这三个步骤来对类进行初始化。如果不出现意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤称为:类加载或类的初始化。
类的加载
java.lang.Class
对象;java.lang.Class
对象。类的连接
类的初始化
类的初始化步骤:
1. 假如类还未被加载和连接,则程序先加载并连接该类;
2. 假如该类的直接父类还未被初始化,则先初始化其直接父类;
3. 假如类中有初始化语句,则系统依次执行这些初始化语句。
注意:在执行第2步的时候,系统对直接父类的初始化步骤也遵循初始化步骤1~3
所以JVM最先初始化的总是Object类
类的初始化时机:
创建类的实例;
调用类的类方法(静态方法);
访问类或者接口的类变量,或者为该类变量赋值;
调用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象;
初始化某个类的子类;
直接调用 java.exe命令运行某个主类。
类加载器的作用
作用:
负责将 .class 文件加载到内存,并为之生成对应的 java.lang.Class 对象
现有 Student和Teacher两个类
这两个类若想使用,首先就得通过类加载器加载对应的 .class 文件到内存,每一个 .class 文件中都应该包含:成员变量、构造方法、成员方法等信息。而每一个 .class 文件都包含这些信息,也就是说所有的 .class文件都有这样的信息,那么我们有没有一个类来描述这些信息呢?肯定是有的,这个类就是 Class类,所以这个类就是所有 .class对象所对应的类型。我们通过Class类里面的对象去使用这些成员变量、成员方法等,而不再通过 Student、Teacher去直接使用。
总结:
Java反射机制是指在运行时去获取一个类的变量和方法信息,然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大地增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍可以扩展。
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 中添加字符串对象
*/
@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);
}