在 Java 开发中,反射机制是一个强大且灵活的工具,它允许程序在运行时动态地获取类的信息、创建对象、调用方法和访问字段等。本文将结合代码示例和图示,深入探讨以下四个问题:
-
类信息来自哪里?
-
获取类信息时加不加
declared
的区别是什么? -
如何使用类信息进行赋值操作?
-
什么是暴力反射?如何使用暴力反射?
一、类信息来自哪里?
类信息的来源可以分为以下几个阶段:
-
源码阶段:开发者编写的
.java
文件,定义了类的结构,包括字段、方法和构造器等。 -
编译阶段:通过
javac
编译器将.java
文件编译为.class
文件,类的信息被存储在字节码文件中。 -
加载阶段:JVM 的类加载器(
ClassLoader
)将.class
文件加载到内存中,并生成对应的Class
对象。 -
运行阶段:通过反射机制,程序可以动态获取
Class
对象中的字段、方法和构造器等信息。
1. 获取 Class
对象的三种方式
在 Java 中,可以通过以下三种方式获取 Class
对象:
-
通过全类名获取:
Class clazz1 = Class.forName("com.gcby.Student");
-
通过类的
.class
属性获取:Class clazz2 = Student.class;
-
通过实例的
.getClass()
方法获取:Student student = new Student(); Class clazz3 = student.getClass();
2. 三种方式的等价性
通过上述三种方式获取的 Class
对象是同一个对象,可以使用 ==
进行比较:
System.out.println(clazz1 == clazz2); // true
System.out.println(clazz1 == clazz3); // true
3. 类信息的加载过程
类信息的加载过程如下图所示:
-
源码文件:
xxx.java
。 -
编译生成字节码文件:
xxx.class
。 -
类加载器加载字节码文件:生成
Class
对象,包含字段、方法和构造器等信息。 -
运行时访问类信息:通过反射机制动态获取类信息。
二、Student
类的定义
为了更好地理解反射机制,我们先来看一下 Student
类的定义:
package com.gcby;public class Student {private String name; // 私有字段public int age; // 公共字段double height; // 默认访问权限字段protected char sex; // 受保护字段// 私有方法private void run() {System.out.println("Running...");}// 公共方法public int getAge() {return this.age;}// 默认访问权限方法String getNameString(String name) {return name;}// 受保护方法protected void run(String a, int b) {System.out.println(a + " " + b);}// 构造器public Student(String name, int age, double height, char sex) {this.name = name;this.age = age;this.height = height;this.sex = sex;}public Student(double height, char sex) {this.height = height;this.sex = sex;}
}
三、获取类信息时加不加 declared
的区别
在使用反射机制获取类信息时,getDeclaredFields()
、getDeclaredMethods()
和 getDeclaredConstructors()
方法与 getFields()
、getMethods()
和 getConstructors()
方法有显著区别:
-
getDeclared
方法:-
获取当前类中声明的所有字段、方法或构造器,包括私有(
private
)、受保护(protected
)、默认(包访问权限)和公共(public
)的成员。 -
不会获取父类的成员。
-
-
get
方法:-
获取当前类及其父类中所有公共(
public
)的字段、方法或构造器。 -
不会获取非公共成员。
-
1. 示例代码
获取字段
Field[] fields = clazz.getDeclaredFields(); // 获取所有声明的字段
Field[] publicFields = clazz.getFields(); // 获取所有公共字段
获取方法
Method[] methods = clazz.getDeclaredMethods(); // 获取所有声明的方法
Method[] publicMethods = clazz.getMethods(); // 获取所有公共方法
获取构造器
Constructor[] constructors = clazz.getDeclaredConstructors(); // 获取所有声明的构造器
Constructor[] publicConstructors = clazz.getConstructors(); // 获取所有公共构造器
2. 示例输出
假设 Student
类如下:
public class Student {private String name;public int age;double height;protected char sex;private void run() {}public int getAge() { return age; }
}
使用 getDeclaredFields()
:
Field[] fields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
// 输出:
// [field Student.name, field Student.age, field Student.height, field Student.sex]
使用 getFields()
:
Field[] publicFields = clazz.getFields();
System.out.println(Arrays.toString(publicFields));
// 输出:
// [field Student.age]
使用 getDeclaredMethods()
:
Method[] methods = clazz.getDeclaredMethods();
System.out.println(Arrays.toString(methods));
// 输出:
// [method void Student.run(), method int Student.getAge()]
使用 getMethods()
:
Method[] publicMethods = clazz.getMethods();
System.out.println(Arrays.toString(publicMethods));
// 输出:
// [method int Student.getAge()]
四、如何使用类信息进行赋值
通过反射机制,可以动态地为对象的字段赋值,即使字段是私有的。以下是具体步骤:
-
获取类对象:
Class clazz = Class.forName("com.gcby.Student");
-
创建实例:
Constructor constructor = clazz.getConstructor(); Student student = (Student) constructor.newInstance();
-
获取字段并赋值:
Field nameField = clazz.getDeclaredField("name"); nameField.setAccessible(true); // 忽略访问权限修饰符 nameField.set(student, "张三");
-
调用方法:
Method runMethod = clazz.getDeclaredMethod("run"); runMethod.setAccessible(true); runMethod.invoke(student);
1. 示例代码
完整代码如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;public class Test {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.gcby.Student");// 创建实例Constructor constructor = clazz.getConstructor();Student student = (Student) constructor.newInstance();// 获取字段并赋值Field nameField = clazz.getDeclaredField("name");nameField.setAccessible(true);nameField.set(student, "张三");Field ageField = clazz.getDeclaredField("age");ageField.setAccessible(true);ageField.set(student, 18);Field heightField = clazz.getDeclaredField("height");heightField.setAccessible(true);heightField.set(student, 185.5);Field sexField = clazz.getDeclaredField("sex");sexField.setAccessible(true);sexField.set(student, '男');// 调用方法Method runMethod = clazz.getDeclaredMethod("run");runMethod.setAccessible(true);runMethod.invoke(student);Method getAgeMethod = clazz.getDeclaredMethod("getAge");System.out.println("Age: " + getAgeMethod.invoke(student)); // 输出:Age: 18}
}
2. 输出结果
Running...
Age: 18
五、什么是暴力反射?
暴力反射(也称为“强制反射”)是指通过反射机制绕过 Java 的访问控制限制,访问或修改类的私有成员。在 Java 中,私有成员(如私有字段和私有方法)在默认情况下是不可访问的,但通过反射机制可以强制访问这些成员。
1. 暴力反射的原理
暴力反射的核心在于 setAccessible(true)
方法。该方法可以绕过 Java 的访问控制限制,允许访问或修改私有成员。以下是具体步骤:
-
获取类对象:
Class clazz = Class.forName("com.gcby.Student");
-
获取私有字段或方法:
Field privateField = clazz.getDeclaredField("name"); Method privateMethod = clazz.getDeclaredMethod("run");
-
设置访问权限:
privateField.setAccessible(true); privateMethod.setAccessible(true);
-
访问或修改私有成员:
privateField.set(student, "张三"); privateMethod.invoke(student);
2. 示例代码
完整代码如下:
import java.lang.reflect.Field;
import java.lang.reflect.Method;public class Test {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.gcby.Student");// 创建实例Student student = (Student) clazz.newInstance();// 获取私有字段并赋值Field nameField = clazz.getDeclaredField("name");nameField.setAccessible(true);nameField.set(student, "张三");// 获取私有方法并调用Method runMethod = clazz.getDeclaredMethod("run");runMethod.setAccessible(true);runMethod.invoke(student);// 输出结果System.out.println("Name: " + nameField.get(student)); // 输出:Name: 张三}
}
3. 输出结果
Running...
Name: 张三
六、总结
-
类信息的来源:类信息从源码文件经过编译生成字节码文件,再由类加载器加载到内存中,生成
Class
对象。 -
getDeclared
与get
的区别:getDeclared
获取当前类中声明的所有成员,包括私有成员;get
只获取公共成员,包括父类的公共成员。 -
使用类信息赋值:通过反射机制,可以动态获取字段并赋值,即使字段是私有的,也可以通过
setAccessible(true)
忽略访问权限修饰符。 -
暴力反射:暴力反射通过
setAccessible(true)
方法绕过 Java 的访问控制限制,允许访问或修改私有成员。虽然暴力反射在某些场景下非常有用,但在实际开发中应谨慎使用,避免破坏类的封装性和安全性。
反射机制是 Java 中的强大工具,但在实际开发中应谨慎使用,避免破坏封装性导致代码难以维护。