反射

类加载器

类的加载

  • 当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中

image-20230927190823488

类的加载时机

  1. 创建类的实例。

  2. 类的静态变量,或者为静态变量赋值。

  3. 类的静态方法。

  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。

  5. 初始化某个类的子类。

  6. 直接使用java.exe命令来运行某个主类。

    以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) throws Exception{
// 类的加载时机
// 1. 创建类的实例。
// Student stu = new Student();

// 2. 类的静态变量,或者为静态变量赋值。
// Person.country = "中国";

// 3. 类的静态方法。
// Person.method();

// 4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
// Class<?> c = Class.forName("com.itheima.demo1_类的加载.Student");

// 5. 初始化某个类的子类。
// Zi zi = new Zi();

// 6. 直接使用java.exe命令来运行某个主类。
}
}

类加载器

类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。

  • Java中有三种类加载器,它们分别用于加载不同种类的class:
    • 启动类加载器(Bootstrap ClassLoader):用于加载系统类库<JAVA_HOME>\bin目录下的class,例如:rt.jar。
    • 扩展类加载器(Extension ClassLoader):用于加载扩展类库<JAVA_HOME>\lib\ext目录下的class。
    • 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Test {
public static void main(String[] args) {
/*
类加载器:
概述:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
如何获取类加载器:
类的字节码对象.getClassLoader()
*/
// 获取Test类的类加载器
ClassLoader c1 = Test.class.getClassLoader();
System.out.println(c1);// AppClassLoader

// 获取Student类的类加载器
ClassLoader c2 = Student.class.getClassLoader();
System.out.println(c2);// AppClassLoader

// 获取String类的类加载器
ClassLoader c3 = String.class.getClassLoader();
System.out.println(c3);// null
//API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null

System.out.println("====================委派机制=================");
System.out.println(c1.getParent());// PlatformClassLoader
System.out.println(c1.getParent().getParent());// null


}
}

反射的概述

反射的引入

  • 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
1
通过反射技术对象类进行了解剖得到了类的所有成员。

反射的概念

1
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)

使用反射操作类成员的前提

1
要获得该类字节码文件对象,就是Class对象

反射在实际开发中的应用

1
2
* 开发IDE(集成开发环境),比如IDEA,Eclipse
* 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits....

Class对象的获取方式

1
2
3
4
* 方式1: 通过类名.class获得
* 方式2:通过对象名.getClass()方法获得
* 方式3:通过Class类的静态方法获得: static Class forName("类全名")
* 每一个类的Class对象都只有一个。
  • 示例代码
1
2
3
4
5
6
7
8
public class Student {
private String name;

public void method1(){

}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test {
public static void main(String[] args) throws Exception{
/*
Class对象的获取:
通过类名.class获得
通过对象名.getClass()方法获得
通过Class类的静态方法获得: static Class forName("类全名")
*/
// 1.方式一:通过类名.class获得
Class<Student> c1 = Student.class;
System.out.println(c1);

// 2.方式二:通过对象名.getClass()方法获得
Student stu = new Student();
Class<? extends Student> c2 = stu.getClass();
System.out.println(c2);

// 3.方式三:通过Class类的静态方法获得: static Class forName("类全名")
Class<?> c3 = Class.forName("com.itheima.demo2_Class对象的获取.Student");
System.out.println(c3);

// 问题:一个类只有一个字节码对象(Class对象)
System.out.println(c1 == c2);// true
System.out.println(c1 == c3);// true
}
}

Class类常用方法

1
2
3
String getSimpleName(); 获得类名字符串:类名
String getName(); 获得类全名:包名+类名
T newInstance() ; 创建Class对象关联类的对象
  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类名字符串:类名
System.out.println(c.getSimpleName());
// 获得类全名:包名+类名
System.out.println(c.getName());
// 创建对象
Student stu = (Student) c.newInstance();
System.out.println(stu);
}
}

反射之操作构造方法

Constructor类概述

1
2
3
4
5
反射之操作构造方法的目的
* 获得Constructor对象来创建类的对象。

Constructor类概述
* 类中的每一个构造方法都是一个Constructor类的对象

通过反射获取类的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
Class类中与Constructor相关的方法 
1. Constructor getConstructor(Class... parameterTypes)
* 根据参数类型获得对应的Constructor对象。
* 只能获得public修饰的构造方法
2. Constructor getDeclaredConstructor(Class... parameterTypes)
* 根据参数类型获得对应的Constructor对象
* 可以是publicprotected、(默认)、private修饰符的构造方法。
3. Constructor[] getConstructors()
获得类中的所有构造方法对象,只能获得public
4. Constructor[] getDeclaredConstructors()
获得类中的所有构造方法对象
可以是publicprotected、(默认)、private修饰符的构造方法。

通过反射执行构造方法

1
2
3
4
5
Constructor对象常用方法
1. T newInstance(Object... initargs)
根据指定的参数创建对象
2. void setAccessible(true)
设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Student {
public String name;
public int age;

public Student() {
}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public Student(String name) {
this.name = name;
}

private Student(int age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class Test {
public static void main(String[] args) throws Exception{
/*
Constructor类概述
类中的每一个构造方法都是一个Constructor类的对象

反射之操作构造方法的目的
获得Constructor对象来创建类的对象。

使用:
1.如何通过反射获取一个类中的构造方法:Class类的方法
1. Constructor getConstructor(Class... parameterTypes)
* 根据参数类型获得对应的Constructor对象。
* 只能获得public修饰的构造方法
2. Constructor getDeclaredConstructor(Class... parameterTypes)
* 根据参数类型获得对应的Constructor对象
* 可以是public、protected、(默认)、private修饰符的构造方法。

3. Constructor[] getConstructors()
获得类中的所有构造方法对象,只能获得public的
4. Constructor[] getDeclaredConstructors()
获得类中的所有构造方法对象
可以是public、protected、(默认)、private修饰符的构造方法。

2.如何通过反射执行获取的构造方法:Constructor的方法
1. T newInstance(Object... initargs)
根据指定的参数创建对象
2. void setAccessible(true)
设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消

*/
// 获取Student类的Class对象
Class<Student> c = Student.class;

// 获取单个构造方法
// 获取无参数的构造方法
Constructor<Student> con1 = c.getDeclaredConstructor();
System.out.println(con1);

// 获取满参构造方法
Constructor<Student> con2 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(con2);

// 获取私有构造方法
Constructor<Student> con3 = c.getDeclaredConstructor(int.class);
System.out.println(con3);

System.out.println("================================");
// 获取所有构造方法
Constructor<?>[] arr1 = c.getDeclaredConstructors();
for (Constructor<?> con : arr1) {
System.out.println(con);
}

System.out.println("================================");
// 通过执行con1表示的构造方法来创建Student对象
Student stu1 = con1.newInstance();
System.out.println(stu1);// Student{name='null', age=0}

// 通过执行con2表示的构造方法来创建Student对象
Student stu2 = con2.newInstance("张三", 18); // 这里是可以放到配置文件的,所以相对灵活
System.out.println(stu2);// Student{name='张三', age=18}

// 取消con3表示的构造方法的权限检查
con3.setAccessible(true);

// 通过执行con3表示的构造方法来创建Student对象
Student stu3 = con3.newInstance(19);
System.out.println(stu3);// Student{name='null', age=19}

}
}

反射之操作成员方法

Method类概述

1
2
3
4
反射之操作成员方法的目的
* 操作Method对象来调用成员方法
Method类概述
* 每一个成员方法都是一个Method类的对象。

通过反射获取类的成员方法

1
2
3
4
5
6
7
8
9
10
11
12
Class类中与Method相关的方法
* Method getMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,只能获得public

* Method getDeclaredMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,包括publicprotected、(默认)、private

* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

* Method[] getDeclaredMethods();
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包括publicprotected、(默认)、private

通过反射执行成员方法

1
2
3
4
5
6
Method对象常用方法
* Object invoke(Object obj, Object... args)
* 调用指定对象obj的该方法
* args:调用方法时传递的参数
* void setAccessible(true)
设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Student {

public void show1() {
System.out.println("show1 方法...");
}

public void show2(int num) {
System.out.println("show2 方法...num: " + num);
}

private void show3() {
System.out.println("show3 方法...");
}

private void show4(String str) {
System.out.println("show1 方法...str: " + str);
}

public int show5(int num) {
System.out.println("show5 方法...num: " + num);
return 100;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class Test {
public static void main(String[] args) throws Exception {
/*
反射之操作成员方法:
Method类概述
每一个成员方法都是一个Method类的对象。
反射之操作成员方法的目的
操作Method对象来调用成员方法
使用:
1.如何通过反射获取类中的成员方法:Class类的方法
* Method getMethod(String name,Class... args);
* 根据方法名和参数类型获得对应的构造方法对象,只能获得public的

* Method getDeclaredMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的

* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

* Method[] getDeclaredMethods();
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的

2.如何通过反射执行获取类中成员方法: Method类的方法
* Object invoke(Object obj, Object... args)
* 参数1:调用方法的对象
* 参数2:调用方法时传递的实际参数
* 返回值: 执行的方法的返回值

* void setAccessible(true)
设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消

*/
// 获取Student类的Class对象
Class<Student> c = Student.class;

// 获取单个方法
// 通过反射获取show1方法
Method m1 = c.getDeclaredMethod("show1");
System.out.println(m1);

// 通过反射获取show2方法
Method m2 = c.getDeclaredMethod("show2", int.class);
System.out.println(m2);

// 通过反射获取show3方法
Method m3 = c.getDeclaredMethod("show3");
System.out.println(m3);

// 通过反射获取show4方法
Method m4 = c.getDeclaredMethod("show4", String.class);
System.out.println(m4);

// 通过反射获取show5方法
Method m5 = c.getDeclaredMethod("show5", int.class);
System.out.println(m5);

System.out.println("==============================");
// 获取所有方法
Method[] arr = c.getDeclaredMethods();
for (Method m : arr) {
System.out.println(m);
}

System.out.println("==============================");
// 通过反射创建Student对象
Student stu = c.newInstance();

// 通过反射执行m1表示的show1方法
m1.invoke(stu);

// 通过反射执行m2表示的show2方法
m2.invoke(stu,10);

// 取消m3表示的方法的权限检查
m3.setAccessible(true);

// 通过反射执行m3表示的show3方法
m3.invoke(stu);

// 取消m4表示的方法的权限检查
m4.setAccessible(true);

// 通过反射执行m4表示的show4方法
m4.invoke(stu,"itheima");

// 通过反射执行m5表示的show5方法
Object res = m5.invoke(stu, 20);// int res = stu.show5(20);
System.out.println(res);// 100

}
}

invoke方法理解

数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 反射测试类
* @author nbchen
* @date 2019/08/01
*/
public class ReflectClass {
    /**
     * 反射测试方法,接收字符串数组,遍历输出
     * @param arg
     */
    public void reflectMethod(String[] arg) {
        for (String s : arg) {
            System.out.println(s);
        }
    }
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 测试反射中的invoke方法
* @author nbchen
* @date 2019/08/01
*/
public class TestInvoke {
    @Test
    public void test() throws Exception {
        // 获取字节码对象
        Class<ReflectClass> clazz = (Class<ReflectClass>) Class.forName("com.nbchen.invoke.ReflectClass");
        // 获取一个对象
        Constructor<ReflectClass> con = clazz.getConstructor();
        ReflectClass instance = con.newInstance();
        String[] testArr = new String[]{"aa","bb"};
        // 获取Method对象
        Method method = clazz.getMethod("reflectMethod",String[].class);
        // 调用invoke方法
        /**
         * 报错:java.lang.IllegalArgumentException: wrong number of arguments
         */
        // method.invoke(instance,testArr);

        // 解决办法1
        // method.invoke(instance, (Object) testArr);
        // 解决办法2
        method.invoke(instance, new Object[]{testArr});
    }
}

小结:

image-20230927190809823

invoke方法的参数,

第一个参数是一个Object类型,也就是调用该方法的对象,

第二个参数是一个可变参数类型,这个可变参数类型怎么能传递给一个数组类型呢?一个是多个参数。一个是一个数组参数,显然参数的个数不匹配,怎么解决呢?

解决办法就是将可变参数变成一个参数:

    1.将传递进去的s强转为Object类型

    2.将s重新包装成一个Object数组

扩展反射操作成员方法案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Test {
public static void main(String[] args)throws Exception {
/*
Method类:
public String getName() 获取方法名
*/
// 需求: 访问Student类中的成员
// 1.获取该类的字节码对象
Class<Student> c = Student.class;

// 2.获取所有构造方法
Constructor<?>[] cons = c.getDeclaredConstructors();

// 3.遍历所有的构造方法
Student stu = null;
for (Constructor<?> con : cons) {
stu = (Student) con.newInstance();
}

// 4.获取所有的成员方法
Method[] methods = c.getDeclaredMethods();

// 5.遍历所有的成员方法
for (Method m : methods) {
if (m.getName().equals("show1")){
m.invoke(stu);
}

if (m.getName().equals("show2")){
m.invoke(stu,10);
}

if (m.getName().equals("show5")){
Object res = m.invoke(stu, 20);
System.out.println(res);
}
// ...
}
}
}

反射之操作成员变量【自学】

Field类概述

1
2
3
4
5
反射之操作成员变量的目的
* 通过Field对象给对应的成员变量赋值和取值

Field类概述
* 每一个成员变量都是一个Field类的对象。

通过反射获取类的成员变量

1
2
3
4
5
6
7
8
9
Class类中与Field相关的方法
* Field getField(String name);
* 根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
* 根据成员变量名获得对应Field对象,包括publicprotected、(默认)、private
* Field[] getFields();
* 获得所有的成员变量对应的Field对象,只能获得public
* Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,包括publicprotected、(默认)、private

通过反射访问成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Field对象常用方法
void set(Object obj, Object value)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)

Object get(Object obj)
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)

void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。

setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。

getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

示例代码

1
2
3
4
5
public class Student {
public String name;
private int age;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class Test {
public static void main(String[] args) throws Exception{
/*
Field类概述
* 每一个成员变量都是一个Field类的对象。

反射之操作成员变量的目的
* 通过Field对象给对应的成员变量赋值和取值

使用:
1.如何通过反射获取类的成员变量: Class类的方法
* Field getField(String name);
* 根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
* 根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的

* Field[] getFields();
* 获得所有的成员变量对应的Field对象,只能获得public的
* Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的

2.如何通过反射访问获取的类的成员变量: Field类的方法
Class getType(); 获取属性的类型,返回Class对象。
void set(Object obj, Object value)
Object get(Object obj)
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。

*/
// 获取Student类的Class对象
Class<Student> c = Student.class;
Student stu = c.newInstance();

// 获取单个的成员变量
// 通过反射获取name成员变量
Field f1 = c.getDeclaredField("name");
System.out.println(f1);

// 通过反射获取age成员变量
Field f2 = c.getDeclaredField("age");
System.out.println(f2);

System.out.println("===============================");
// 获取所有成员变量
Field[] arr = c.getDeclaredFields();
for (Field field : arr) {
System.out.println(field);
}

System.out.println("===============================");
// 获取f1表示的name属性的类型
System.out.println(f1.getType());
// 获取f2表示的age属性的类型
System.out.println(f2.getType());

System.out.println("===============================");
// 通过反射给f1表示的name属性赋值
f1.set(stu,"张三" );// 相当于stu.name="张三";

// 取消f2表示的属性的权限检查
f2.setAccessible(true);

// 通过反射给f2表示的age属性赋值
f2.set(stu,18 );

System.out.println("===============================");
// 通过反射获取f1表示的name属性的值
System.out.println(f1.get(stu));// 张三

// 通过反射获取f2表示的age属性的值
System.out.println(f2.get(stu));// 18


}
}