首先,官方的定义是这样的:
Java反射(Reflection)是指在程序运行时,动态获取类的完整结构信息(如构造方法、字段、方法),并能直接操作对象的属性或方法的机制。
正射 vs 反射
用【创建对象并调用方法】举个例子:
正射
就是我们平时写代码的方式,编译期就知道要操作的类是什么,直接 new 对象调用方法:
// 正射:编译期就确定是User类User user = new User();user.setName("张三"); // 直接调用方法反射
动态的方式**,编译期**不知道要操作的类是什么,运行期才通过类名字符串动态获取类信息并操作:
// 反射:运行期才通过字符串"com.example.User"获取类Class<?> clazz = Class.forName("com.example.User");Object user = clazz.getDeclaredConstructor().newInstance(); // 动态创建对象Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);setNameMethod.invoke(user, "张三"); // 动态调用方法原理
反射的底层,其实是操作JVM加载类后生成的 Class对象。
先回顾一下类的加载过程:
编译期: .java文件被编译成.class字节码文件;类加载期:JVM的类加载器(ClassLoader)把 .class文件加载到内存,生成一个java.lang.Class对象(这个对象包含了类的所有结构信息);运行期:反射就是通过这个 Class对象,逆向获取类的构造方法、字段、方法等信息,并进行操作。
说人话就是:Class对象是反射的入口,所有反射操作都从获取Class对象开始。
手撕一下,加深理解
1. 获取Class对象的3种方式
// 方式1:类名.class(最常用,编译期就能获取)Class<User> clazz1 = User.class;// 方式2:对象.getClass()(已有对象时用)User user = new User();Class<? extends User> clazz2 = user.getClass();// 方式3:Class.forName("全类名")(最灵活,框架常用)Class<?> clazz3 = Class.forName("com.example.User");2. 操作构造方法:动态创建对象
Class<?> clazz = Class.forName("com.example.User");// 获取无参构造方法并创建对象Constructor<?> noArgConstructor = clazz.getDeclaredConstructor();Object user1 = noArgConstructor.newInstance();// 获取有参构造方法并创建对象Constructor<?> argConstructor = clazz.getDeclaredConstructor(String.class, int.class);Object user2 = argConstructor.newInstance("张三", 25);3. 操作字段:动态获取/设置属性
Class<?> clazz = Class.forName("com.example.User");Object user = clazz.getDeclaredConstructor().newInstance();// 获取public字段并赋值Field nameField = clazz.getField("name");nameField.set(user, "张三");// 获取private字段并赋值(需打破封装)Field ageField = clazz.getDeclaredField("age");ageField.setAccessible(true); // 关键:打破private访问限制ageField.set(user, 25);4. 操作方法:动态调用方法
Class<?> clazz = Class.forName("com.example.User");Object user = clazz.getDeclaredConstructor().newInstance();// 获取public方法并调用Method setNameMethod = clazz.getMethod("setName", String.class);setNameMethod.invoke(user, "张三");// 获取private方法并调用(需打破封装)Method getAgeMethod = clazz.getDeclaredMethod("getAge");getAgeMethod.setAccessible(true);int age = (int) getAgeMethod.invoke(user);实际应用场景
Java生态里的核心框架几乎都用到了反射,比如:
1. Spring框架的IOC/DI
Spring的IOC容器就是通过反射创建对象并注入依赖的:
在XML里写 <bean id="user" class="com.example.User"/>➡️Spring读取XML,拿到全类名 com.example.User➡️Spring通过 Class.forName() 获取Class对象,动态创建Bean对象➡️如果Bean有依赖(比如 userService 依赖 userDao),Spring再通过反射注入依赖。
2. JDBC加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver"),底层就是反射:首先加载驱动类到JVM,接下来驱动类的静态代码块会自动执行,把驱动注册到 DriverManager 里。
3. 注解处理
比如JUnit的 @Test 注解:JUnit启动后,通过反射扫描所有类,找到带 @Test 注解的方法,再通过反射动态调用这些方法执行测试。
反射的优缺点
优点
灵活性高:运行期才确定要操作的类,不需要在编译期写死; 动态性强:适合开发通用框架(如Spring、MyBatis),框架不需要知道用户会写什么类,通过反射就能动态适配。
缺点
性能低:反射比直接调用方法慢10-100倍(因为需要动态解析类信息),所以高频调用的场景(比如循环里)尽量不用反射; 破坏封装性:可以通过 setAccessible(true)访问private字段/方法,破坏了Java的封装原则;安全性问题:反射可以执行任意代码,可能被恶意利用。