Java安全
Java 基础知识
01概述
02变量
03运算符
04程序控制语句
05数组
06面向对象编程
07代码练习
08重载
09作用域
10构造方法&构造器
11this
12包
13修饰符
14封装
15继承
16super
17覆盖&重写
18多态
19零钱通项目
20类变量&类方法
21抽象类
22接口
23内部类
24枚举
25泛型
26常用API
27lambda表达式
28正则表达式
29异常
30File&IO流
31日志技术
32多线程
33网络编程
01反射
02反序列化
03JVM
04JDBC
05RMI
06JRMP
07JNDI
08CDI
09JPA
10Servlet
11Filter
12MVC模型
13MVC框架
14类的加载机制
15Maven
16注解
17ORM
18CC链
19JNDI注入
Log4j2
-
+
首页
01反射
## 概述 在 Java 中,反射(Reflection) 是指程序在运行时可以获取自身信息(如类的结构、成员变量、方法等),并能动态操作这些信息的能力。 简单说,反射允许程序 “看透” 自身的类结构,甚至在编译期未知类信息的情况下,创建对象、调用方法、修改字段。 Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。 本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。 Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。 通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。 ## 作用 在传统编程中,我们需要在编译期知道类的具体信息(如类名、方法名)才能使用它(例如new User()、user.getName())。 而反射打破了这一限制,让程序能在运行时: - 动态获取任意类的结构信息(类名、父类、接口、字段、方法、注解等); - 动态创建类的实例(无需在编译期知道类名); - 动态调用类的方法(包括私有方法); - 动态修改类的字段值(包括私有字段)。 ## 核心接口或类 Java 的反射功能主要通过java.lang.reflect包和java.lang.Class类实现,核心类如下: | 类 / 接口 | 作用 | | ----------- | ------------------------------------------------------------ | | Class | 反射的 “入口”,代表一个类的字节码对象,存储类的所有元信息(如类名、方法、字段等)。每个类在 JVM 中只有一个Class对象。 | | Constructor | 代表类的构造器,用于创建对象(支持获取参数类型、访问权限,调用构造器实例化对象)。 | | Method | 代表类的方法,用于调用方法(支持获取方法名、参数、返回值,调用方法并传递参数)。 | | Field | 代表类的字段(成员变量),用于获取或修改字段值(支持获取字段名、类型、访问权限,设置字段值)。 | | Modifier | 工具类,用于解析类 / 方法 / 字段的访问修饰符(如public、private、static)。 | ## Class类 Class 类是反射实现的基础。  在程序运行期间,JVM 始终为所有的对象维护一个被称为运行时的类型标识,这个信息跟踪着每个对象所属的类的完整结构信息,包括包名、类名、实现的接口、拥有的方法和字段等。 可以通过专门的 Java 类访问这些信息,这个类就是 Class 类。我们可以把 Class 类理解为类的类型,一个 Class 对象,称为类的类型对象,一个 Class 对象对应一个加载到 JVM 中的一个 .class 文件。 在通常情况下,一定要现有类再有对象,类的正常加载过程: ```java import java.util.Date; public class Test{ public static void main(String[] args) { Date date = new Date(); // 后有对象 System.out.println(date); } ``` 示意图:  需要注意的是,每个类只有一个 Class 对象,也就是说如果我们有第二条 new Date() 语句,JVM 不会再生成一个 Date 的 Class 对象,因为已经存在一个了。 这也使得我们可以利用 == 运算符实现两个类对象比较的操作: ```java System.out.println(date.getClass() == Date.getClass()); // true ``` 那么在加载完一个类后,堆内存的方法区就产生了一个 Class 对象,这个对象就包含了完整的类的结构信息,我们可以通过这个 Class 对象看到类的结构,就好比一面镜子。所以我们形象的称之为:反射。 在通常情况下,一定是先有类再有对象,我们把这个通常情况称为 “正”。那么反射中的这个 “反” 我们就可以理解为根据对象找到对象所属的类(对象的出处) ```java Date date = new Date(); System.out.println(date.getClass()); // "class java.util.Date" ``` 通过反射,也就是调用了 getClass() 方法后,我们就获得了 Date 类对应的 Class 对象,看到了 Date 类的结构,输出了 Date 对象所属的类的完整名称,即找到了对象的出处。 当然,获取 Class 对象的方式不止这一种。 ## 基本用法 以一个简单的User类为例,演示反射的核心操作。 ### 定义目标类 ```java public class User { private String name; // 私有字段 public int age; // 公有字段 // 无参构造器 public User() {} // 有参构造器(私有) private User(String name) { this.name = name; } // 公有方法 public void sayHello() { System.out.println("Hello, " + name); } // 私有方法 private String getNameAndAge() { return name + ", " + age; } } ``` ### 反射操作步骤 1、获取Class对象(反射的入口),Class对象是反射的基础,有三种获取的方式: ```java // 方式1:类名.class(编译期已知类,知道具体类) Class clazz1 = User.class; // 方式2:对象.getClass()(已知实例,通过对象实例 instance.getClass() 获取) User user = new User(); Class clazz2 = user.getClass(); // 方式3:Class.forName("全类名")(通过 Class.forName()传入全类名获取,需处理ClassNotFoundException) Class clazz3 = Class.forName("com.example.User"); //方式4:通过类加载器 xxxClassLoader.loadClass() 传入类路径获取 class clazz4 = ClassLoader.LoadClass("com.xxx.TargetObject"); ``` 2、动态创建对象。 ```java // 1. 获取无参构造器(public),创建对象 Constructor<?> constructor1 = clazz3.getConstructor(); // 获取public无参构造器 User user1 = (User) constructor1.newInstance(); // 调用构造器创建实例 // 2. 获取私有有参构造器(private),并调用(需设置可访问) Constructor<?> constructor2 = clazz3.getDeclaredConstructor(String.class); // getDeclaredConstructor可获取私有构造器 constructor2.setAccessible(true); // 暴力破解:设置私有构造器可访问 User user2 = (User) constructor2.newInstance("Alice"); // 创建实例(传入参数"Alice") ``` 3、动态调用方法。 ```java // 1. 调用公有方法sayHello() Method sayHelloMethod = clazz3.getMethod("sayHello"); // 获取public方法(无参) sayHelloMethod.invoke(user2); // 调用方法:输出 "Hello, Alice" // 2. 调用私有方法getNameAndAge() Method getInfoMethod = clazz3.getDeclaredMethod("getNameAndAge"); // getDeclaredMethod可获取私有方法 getInfoMethod.setAccessible(true); // 暴力破解:允许访问私有方法 String info = (String) getInfoMethod.invoke(user2); // 调用方法 System.out.println(info); // 输出 "Alice, 0"(age默认0) ``` 4、动态修改字段值。 ```java // 1. 修改公有字段age Field ageField = clazz3.getField("age"); // 获取public字段 ageField.set(user2, 25); // 设置user2的age为25(等价于 user2.age = 25) // 2. 修改私有字段name Field nameField = clazz3.getDeclaredField("name"); // 获取private字段 nameField.setAccessible(true); // 暴力破解:允许访问私有字段 nameField.set(user2, "Bob"); // 设置user2的name为Bob(等价于 user2.name = "Bob",但绕过了private修饰符) // 再次调用私有方法验证 String newInfo = (String) getInfoMethod.invoke(user2); System.out.println(newInfo); // 输出 "Bob, 25" ``` ## 原理 反射的底层依赖 JVM 对类的加载机制: 1. 当类被加载到 JVM 时,JVM 会为该类生成一个唯一的Class对象(存储在方法区),包含类的完整元信息(类名、父类、接口、构造器、方法、字段、注解等); 2. 反射 API(Class、Method等)本质是通过调用 JVM 提供的本地方法(如native方法),从Class对象中读取元信息,并动态操作类的成员(如调用方法时,JVM 会根据Method对象找到对应的字节码指令并执行)。  ## 代码示例 Dog.java ```java public class Dog { private String name; Dog(){} Dog(String name){ this.name = name; } void say(){ System.out.println(name + ":" + "汪汪汪!"); } private void say(String message){ System.out.println(name + ":" +message); } @Override public String toString() { // return super.toString(); return "Dog{"+"name="+name+'}'; } } ``` Main.java ```java import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception{ // Dog dog = new Dog("旺旺"); // dog.say(); Class c = Class.forName("Dog"); // Object obj = c.newInstance(); Constructor constructor = c.getDeclaredConstructor(String.class); Object obj= constructor.newInstance("汪汪"); Field f = c.getDeclaredField("name"); f.setAccessible(true); f.set(obj,"大白"); System.out.println(obj); Method method = c.getDeclaredMethod("say",String.class); method.setAccessible(true); method.invoke(obj,"喵喵喵"); } } ```
毛林
2025年10月27日 20:58
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码