Loading...

【面试真题】Java中什么是反射

首先,官方的定义是这样的:

Java反射(Reflection)是指在程序运行时,动态获取类的完整结构信息(如构造方法、字段、方法),并能直接操作对象的属性或方法的机制

正射 vs 反射

用【创建对象并调用方法】举个例子:

正射

就是我们平时写代码的方式,编译期就知道要操作的类是什么,直接 new 对象调用方法:

1
2
3
// 正射:编译期就确定是User类
User user = new User();
user.setName("张三"); // 直接调用方法

反射

动态的方式**,编译期**不知道要操作的类是什么,运行期才通过类名字符串动态获取类信息并操作:

1
2
3
4
5
// 反射:运行期才通过字符串"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对象

先回顾一下类的加载过程:

  1. 编译期.java文件被编译成 .class字节码文件;
  2. 类加载期:JVM的类加载器(ClassLoader)把 .class文件加载到内存,生成一个 java.lang.Class对象(这个对象包含了类的所有结构信息);
  3. 运行期:反射就是通过这个 Class对象,逆向获取类的构造方法、字段、方法等信息,并进行操作。

说人话就是:Class对象是反射的入口,所有反射操作都从获取Class对象开始

手撕一下,加深理解

1. 获取Class对象的3种方式

1
2
3
4
5
6
7
8
9
// 方式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. 操作构造方法:动态创建对象

1
2
3
4
5
6
7
8
9
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. 操作字段:动态获取/设置属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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. 操作方法:动态调用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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的封装原则;
  • 安全性问题:反射可以执行任意代码,可能被恶意利用。

最后更新于 2026-04-05 17:35:33
Code Road Record