Java反射机制
# Java反射机制
# 1. 什么是反射机制
JAVA 反射机制是在运行状态中
,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用
对象的功能称为 Java 语言的反射机制。
# 2. 反射机制作用
- 通过 Java 语言中的反射机制可以操作字节码文件(可以读和修改字节码)文件)
- 通过反射机制可以操作代码片段(Class 文件)
反射机制的相关类:java.lang.reflect.*
# 3. 反射机制相关的重要的类
类 | 含义 |
---|---|
java.lang.Class | 代表整个字节码。代表一个类型,代表整个类。 |
java.lang.reflect.Method | 代表字节码中的方法字节码。代表类中的方法。 |
java.lang.reflect.Constructor | 代表字节码中的构造方法字节码。代表类中的构造方法。 |
java.lang.reflect.Field | 代表字节码中的属性字节码。代表类中的成员变量(静态变量 + 实例变量)。 |
- 注:必须先获得 Class 才能获取 Method(方法)、Constructor(构造方法)、Field(成员变量)。
public class User{
// Field
int age;
// Constructor
public User(){
}
public User(int age){
this.age = age;
}
// Method
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 4. 获取Class的三方式
要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取 java.lang.Class
实例?
方式 | 备注 |
---|---|
Class.forName("完整类名带包名") | 静态方法 |
对象.getClass() | |
任何类型.class |
注:以上三种方式返回值都是 Class类型。
// 1.通过类的全路径获取字节码对象
Class alunbarClass1 = Class.forName("com.xxx.TargetObject");
// 2.通过实例对象获取字节码对象
Date date = new Date();
Class alunbarClass2 = date.getClass();
// 3.通过类名获取字节码对象
Class alunbarClass = TargetObject.class;
2
3
4
5
6
7
# 5. 通过反射实例化对象
语法:对象.newInstance()
注:newInstance()
方法内部实际上调用了 无参数构造方法,必须保证无参构造存在才可以,否则会抛出 java.lang.InstantiationException
异常。
class ReflectTest02{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 通过反射机制,获取 Class,通过 Class 来实例化对象
Class c = Class.forName("javase.reflectBean.User");
// newInstance() 这个方法默认会调用 User 这个类的无参数构造方法,完成对象的创建。
// 重点是:newInstance() 调用的是无参构造,必须保证无参构造是存在的!
Object obj = c.newInstance();
System.out.println(obj);
}
}
2
3
4
5
6
7
8
9
10
11
# 6. 使用Class.forName实现类加载
当你需要在Java中仅执行某个类的静态代码块,而不执行其他代码,你可以使用Class.forName("完整类名")
方法。这个方法的作用是:
- 触发类的加载: 当调用
Class.forName("完整类名")
时,Java虚拟机会加载指定的类。这是一种显式的类加载方式。 - 执行静态代码块: 类加载的过程中,该类中定义的静态代码块会被自动执行。静态代码块通常用于进行类初始化操作,如设置静态变量的初始值或执行一次性的设置操作。
- 不执行其他代码: 通过这种方式,类的其他部分(如实例初始化代码)不会被执行,除非你显式地创建类的实例或调用类的其他静态方法。
这种机制特别适用于一些需要初始化但不需要实例化对象的场景,如在使用JDBC连接数据库时加载和注册数据库驱动。
public class MyDatabaseDriver {
static {
// 这里是静态代码块,用于执行数据库驱动的注册等初始化操作
System.out.println("数据库驱动已注册");
}
// 类的其他部分
}
public class Main {
public static void main(String[] args) {
try {
// 这里只会触发 MyDatabaseDriver 类的加载和静态代码块的执行
Class.forName("MyDatabaseDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在这个例子中,Class.forName("MyDatabaseDriver")
会导致MyDatabaseDriver
类被加载,并执行其静态代码块,但不会执行类的其他代码或创建类的实例。
# 7. 反射Filed(字段)
# Class类方法
Class
类是用来表示和操作类本身的。每个类都有一个Class
对象,它提供了获取类相关信息和进行操作的方法。
方法名 | 备注 |
---|---|
public T newInstance() | 创建对象 |
public String getName() | 返回完整类名带包名 |
public String getSimpleName() | 返回类名 |
public Field[] getFields() | 返回类中 public 修饰的属性 |
public Field[] getDeclaredFields() | 返回类中所有的属性 |
public Field getDeclaredField(String name) | 根据属性名name获取指定的属性 |
public native int getModifiers() | 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号(一般配合 Modifier 类的 toString(int x) 方法使用) |
public Method[] getDeclaredMethods() | 返回类中所有的实例方法 |
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 根据方法名 name 和方法形参获取指定方法 |
public Constructor<?>[] getDeclaredConstructors() | 返回类中所有的构造方法 |
public Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 根据方法形参获取指定的构造方法 |
--- | --- |
public native Class<? super T> getSuperclass() | 返回调用类的父类 |
public Class<?>[] getInterfaces() | 返回调用类实现的接口集合 |
# Field类方法
Field
类是java.lang.reflect
包的一部分,它提供了一系列方法来动态地访问和操作类的字段。这些方法允许您在运行时获取字段的信息(如名称、类型和修饰符)以及对字段的值进行设置和读取。
方法名 | 备注 |
---|---|
public String getName() | 返回属性名 |
public int getModifiers() | 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号(一般配合 Modifier 类的 toString(int x) 方法使用) |
public Class<?> getType() | 以 Class 类型,返回属性类型(一般配合 Class 类的 getSimpleName() 方法使用) |
public void set(Object obj, Object value) | 设置属性值 |
public Object get(Object obj) | 读取属性值 |
# 反射设置/读取对象属性(掌握)
// 反射操作对象属性的示例
public class ReflectTest07 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 传统方式操作对象的属性
Student student = new Student();
// 给student对象的age属性赋值18
student.age = 18;
// 输出student对象的age属性的值
System.out.println(student.age);
// 使用反射机制操作对象的属性
// 1. 获取Student类的Class对象
Class studentClass = Class.forName("javase.reflectBean.Student");
// 2. 通过Class对象实例化Student类的对象,这里使用无参数的构造函数
Object obj = studentClass.newInstance(); // obj是Student类型的对象
// 3. 获取Student类的名为"age"的字段
Field ageField = studentClass.getDeclaredField("age");
// 4. 使用Field对象给obj对象的"age"字段设置值
// 设置操作涉及三个要素:对象(obj),字段(ageField)和新值(20)
ageField.set(obj, 20);
// 5. 使用Field对象读取obj对象的"age"字段的值
// 读取操作涉及两个要素:对象(obj)和字段(ageField)
System.out.println(ageField.get(obj)); // 输出obj对象的"age"字段的值
}
}
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
# set()可以设置私有属性嘛?
默认不可以,需要打破封装,才可以。
方法 | 备注 |
---|---|
public void setAccessible(boolean flag) | 默认 false,设置为 true 为打破封装 |
public class Test {
public void static main() {
// 可以访问私有的属性吗?
Field nameField = studentClass.getDeclaredField("name");
// 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会)
// 这样设置完之后,在外部也是可以访问 private 的
nameField.setAccessible(true);
// 给 name 属性赋值
nameField.set(obj, "xiaowu");
// 获取 name 属性的值
System.out.println(nameField.get(obj));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 反编译Field(了解)
// 通过反射机制,反编译一个类的属性 Field(了解一下)
class ReflectTest06{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class studentClass = Class.forName("javase.reflectBean.Student");
// Class 类的 getName方法
s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
// 获取所有的属性
Field[] fields = studentClass.getDeclaredFields();
for (Field f : fields){
s.append("\t");
// 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
// 用 Modifier 类的 toString 转换成字符串
s.append(Modifier.toString(f.getModifiers()));
if (f.getModifiers() != 0) s.append(" ");
s.append(f.getType().getSimpleName()); // 获取属性的类型
s.append(" ");
s.append(f.getName()); // 获取属性的名字
s.append(";\n");
}
s.append("}");
System.out.println(s);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 8. 反射Method(方法)
# Method类方法
Method
类是java.lang.reflect
包的一部分,用于表示和操作类的方法。通过Method
类,您可以在运行时动态地访问和调用类的任何方法。
方法名 | 备注 |
---|---|
public String getName() | 返回方法名 |
public int getModifiers() | 获取方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号(一般配合Modifier类的 toString(int x) 方法使用) |
public Class<?> getReturnType() | 以 Class 类型,返回方法类型(一般配合 Class 类的 getSimpleName() 方法使用) |
public Class<?>[] getParameterTypes() | 返回方法的修饰符列表,(一个方法的参数可能会有多个),结果集一般配合 Class 类的 getSimpleName() 方法使用 |
public Object invoke(Object obj, Object… args) | 调用方法 |
注: Method 类中 invoke()
使用注意事项:
方法.invoke(对象, 实参列表);
# 反射调用对象方法(掌握)
/*
* 通过反射调用对象方法的示例。
* 反射增加了代码的通用性,使得可变化的内容可以配置化。修改配置后,
* 创建的对象和调用的方法可能改变,但Java代码本身无需修改,这展示了反射的强大灵活性。
*/
class ReflectTest10 {
public static void main(String[] args) throws Exception {
// 不使用反射机制调用方法的情况,首先创建UserService对象
UserService userService = new UserService();
// 调用login方法
System.out.println(userService.login("admin", "123") ? "登入成功!" : "登入失败!");
// 使用反射机制调用方法
// 1. 获取UserService类的Class对象
Class userServiceClass = Class.forName("javase.reflectBean.UserService");
// 2. 利用Class对象创建UserService的实例
Object obj = userServiceClass.newInstance();
// 3. 获取UserService类的login方法的Method对象,需要传入方法名和参数类型
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
// 4. 使用Method对象调用invoke方法,相当于调用login方法
/**
* 参数1:obj对象(调用方法的对象实例)
* 参数2:实参列表(这里是登录的用户名和密码)
*/
Object returnValue = loginMethod.invoke(obj, "admin", "123");
// 注意:如果方法的返回值类型是void,invoke方法将返回null
System.out.println(returnValue);
}
}
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
# 反编译Method
/*
了解一下,不需要掌握(反编译一个类的方法)
*/
class ReflectTest09{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class userServiceClass = Class.forName("java.lang.String");
s.append(Modifier.toString(userServiceClass.getModifiers()));
s.append(" class ");
s.append(userServiceClass.getSimpleName());
s.append(" {\n");
// 获取所有的Method(包括私有的!)
Method[] methods = userServiceClass.getDeclaredMethods();
for (Method m : methods){
s.append("\t");
// 获取修饰符列表
s.append(Modifier.toString(m.getModifiers()));
s.append(" ");
// 获取方法的返回值类型
s.append(m.getReturnType().getSimpleName());
s.append(" ");
// 获取方法名
s.append(m.getName());
s.append("(");
// 方法的修饰符列表(一个方法的参数可能会有多个。)
Class[] parameterTypes = m.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++){
s.append(parameterTypes[i].getSimpleName());
if (i != parameterTypes.length - 1) s.append(", ");
}
s.append(") {}\n");
}
s.append("}");
System.out.println(s);
}
}
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
# 9. 反射Constructor(构造方法)
# Constructor类方法
Constructor
类是java.lang.reflect
包的一部分,用于表示和操作类的构造方法。通过Constructor
类,您可以在运行时动态地访问、创建类的实例,并调用其构造方法。
方法名 | 备注 |
---|---|
public String getName() | 返回构造方法名 |
public int getModifiers() | 获取构造方法的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号(一般配合 Modifier 类的 toString(int x) 方法使用) |
public Class<?>[] getParameterTypes() | 返回构造方法的修饰符列表,(一个方法的参数可能会有多个)结果集一般配合 Class 类的 getSimpleName() 方法使用 |
public T newInstance(Object … initargs) | 创建对象(参数为创建对象的数据) |
# 创建对象两步骤
- 先获取到这个有参数的构造方法,用 `ClassgetDeclaredConstructor() 方法获取
- 调用构造方法new对象,用 Constructor 类的
newInstance()
方法 new 对象
# 反射获取构造方法实例化对象(掌握)
public class ReflectTest12 {
public static void main(String[] args) throws Exception {
// 传统方式创建对象,直接通过构造方法创建Vip类的实例
Vip vip1 = new Vip(); // 使用无参数构造方法创建实例
Vip vip2 = new Vip(123, "zhangsan", "2001-10-19", false); // 使用有参数构造方法创建实例
// 通过类的全限定名使用Class.forName静态方法获取Vip类的Class对象
Class vipClass = Class.forName("javase.reflectBean.Vip");
// 1. 使用无参数构造方法创建对象
// 利用Class对象的newInstance方法创建Vip的实例,这种方式仅适用于无参数构造方法
// 从Java 9开始,Class的newInstance方法已被弃用,建议使用Constructor的newInstance方法
Object obj1 = vipClass.newInstance();
System.out.println(obj1);
// 1. 获取有参数的构造方法
// 使用getDeclaredConstructor方法获取Vip类的特定构造方法对象,需要传入构造方法的参数类型
Constructor c1 = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
// 2. 使用Constructor对象的newInstance方法创建Vip的实例
// newInstance方法的参数应与构造方法的参数匹配,用于传递给构造方法的实际参数
Object obj2 = c1.newInstance(321, "lsi", "1999-10-11", true);
System.out.println(obj2);
// 1. 获取Vip类的无参数构造方法,与有参数构造方法获取方式相似,但这里不传递任何参数类型
Constructor c2 = vipClass.getDeclaredConstructor();
// 2. 使用Constructor对象的newInstance方法创建Vip的实例, 由于是无参数构造方法,不需要传递任何参数
Object obj3 = c2.newInstance();
System.out.println(obj3);
}
}
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
笔记
- 无参数构造方法创建对象(隐式获取无参数构造方法):通过
Class
对象的newInstance()
方法创建了一个无参数构造方法的实例。注意,这种方式在Java 9及之后的版本中已被弃用。 - 有参数构造方法创建对象:通过
Class
对象的getDeclaredConstructor()
方法获取指定参数类型的构造方法,然后使用Constructor
对象的newInstance()
方法创建实例,并传入构造方法需要的参数。 - 无参数构造方法创建对象(显式获取无参数构造方法):通过获取无参数构造方法的
Constructor
对象并调用其newInstance()
方法来创建实例,展示了如何显式使用无参数构造方法进行对象实例化。
# 反编译Constructor(了解)
/*
反编译一个类的 Constructor 构造方法。
*/
class ReflectTest11{
public static void main(String[] args) throws ClassNotFoundException {
StringBuilder s = new StringBuilder();
Class vipClass = Class.forName("javase.reflectBean.Vip");
// public class UserService {
s.append(Modifier.toString(vipClass.getModifiers()));
s.append(" class ");
s.append(vipClass.getSimpleName());
s.append("{\n");
Constructor[] constructors = vipClass.getDeclaredConstructors();
for (Constructor c : constructors){
// public Vip(int no, String name, String birth, boolean sex) {
s.append("\t");
s.append(Modifier.toString(c.getModifiers()));
s.append(" ");
// s.append(c.getName()); // 包名 + 类名
s.append(vipClass.getSimpleName()); // 类名
s.append("(");
Class[] parameterTypes = c.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++){
s.append(parameterTypes[i].getSimpleName());
if (i != parameterTypes.length - 1 ) s.append(", ");
}
s.append("){}\n");
}
s.append("}");
System.out.println(s);
}
}
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
# 10. 注意
在Java的反射机制中,对于类的成员(属性、方法和构造方法)来说,它们的识别和区分主要依赖于以下几个关键要素:
- 属性(Field):对于类的属性来说,最重要的是它的名字。属性的名字在类中是唯一的,因此您可以仅通过名字来获取特定的属性。
- 实例方法(Method):对于类的方法来说,其识别不仅依赖于方法的名字,还依赖于方法的形参列表(参数类型)。这是因为在Java中允许方法重载,所以仅凭方法名是不足以区分不同的方法的。方法的唯一性是由其名字和形参列表共同决定的。
- 构造方法(Constructor):对于构造方法来说,由于它们没有名字(它们的名字总是与类名相同),因此其区分完全依赖于形参列表。不同的构造方法会有不同的参数类型或数量。
# 11. 获取类的父类以及实现的接口
在Java中,通过反射API提供的Class
类的方法可以获取到一个类的父类以及它实现的所有接口的信息。这两个功能主要通过以下两个Class
类中的方法实现:
public native Class<? super T> getSuperclass()
- 这个方法返回当前
Class
对象所表示的类的父类的Class
对象。如果该类是基础类(如Object
类或者接口),则返回null
。
- 这个方法返回当前
public Class<?>[] getInterfaces()
- 这个方法返回当前
Class
对象所表示的类所实现的所有接口的Class
对象数组。如果该类没有实现任何接口,将返回一个长度为0的数组。
- 这个方法返回当前
/*
重点:给你一个类,怎么获取这个类的父类,以及实现了哪些接口?
*/
class ReflectTest13 {
public static void main(String[] args) throws Exception {
// 选择java.lang.String类作为反射的目标
Class stringClass = Class.forName("java.lang.String");
// 获取并打印String类的父类
Class superclass = stringClass.getSuperclass();
System.out.println("String类的父类是: " + superclass.getName());
// 获取并打印String类实现的所有接口
Class[] interfaces = stringClass.getInterfaces();
System.out.print("String类实现的接口有: ");
for (Class i : interfaces) {
System.out.println(i.getName());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 12. 总结
以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是scholar,一个在互联网行业的小白,立志成为更好的自己。
如果你想了解更多关于scholar (opens new window),可以关注公众号-书生带你学编程,后面文章会首先同步至公众号。