Java 反射机制深度解析:类信息的来源、declared 的区别、赋值操作及暴力反射
在 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 中的强大工具,但在实际开发中应谨慎使用,避免破坏封装性导致代码难以维护。