Java反射机制入门
1. 引入:为什么需要反射?
1.1 正常创建对象的局限性
正常情况下,我们创建对象是这样的:
java
Student s = new Student();
s.study();
问题 :这种写法在编译时就必须知道类的全名。如果类的名字是在程序运行时才知道(比如从配置文件读取),怎么办?
properties
# config.properties
className = com.example.Student
1.2 反射的解决方案
反射 允许程序在运行时动态地获取类的信息、创建对象、调用方法。
java
// 运行时才知道要创建哪个类
String className = 从配置文件读取到的类名;
Class clazz = Class.forName(className); // 获取类对象
Object obj = clazz.newInstance(); // 创建实例
核心理解:反射就是把Java类本身当成对象来操作。
2. 类的加载机制
2.1 类加载的三个阶段
编写代码 → 编译 → 加载 → 连接 → 初始化 → 使用 → 卸载
↓
.java .class
| 阶段 | 说明 |
|---|---|
| 加载 | 将.class文件读入内存,创建Class对象 |
| 连接 | 验证、准备(分配内存给静态变量)、解析(符号引用转直接引用) |
| 初始化 | 执行静态代码块和静态变量赋值 |
2.2 类加载器(ClassLoader)
类加载器负责把.class文件加载到内存:
java
// 获取类加载器
ClassLoader loader = Student.class.getClassLoader();
System.out.println(loader); // AppClassLoader
// 类加载器的层级
// BootstrapClassLoader(根加载器,加载rt.jar)
// ↑
// ExtensionClassLoader(扩展加载器)
// ↑
// AppClassLoader(应用加载器,加载自己写的类)
2.3 类加载的时机
类第一次被使用时才会加载:
new对象时- 调用静态方法/静态变量时
- 反射调用时
java
// 下面这行代码会触发Student类加载
Student s = new Student();
3. 获取Class对象的三种方式
| 方式 | 代码 | 适用场景 |
|---|---|---|
| 类名.class | Class c = Student.class; |
已知类型 |
| 对象.getClass() | Class c = s.getClass(); |
已有对象 |
| Class.forName() | Class c = Class.forName("com.example.Student"); |
只知道全类名(最常用) |
java
public class GetClassDemo {
public static void main(String[] args) throws Exception {
// 方式1:类名.class
Class c1 = Student.class;
// 方式2:对象.getClass()
Student s = new Student();
Class c2 = s.getClass();
// 方式3:Class.forName()(最灵活)
Class c3 = Class.forName("com.Student");
System.out.println(c1 == c2); // true
System.out.println(c1 == c3); // true(同一个Class对象)
}
}
重点:同一个类在JVM中只存在一个Class对象,三种方式获取的是同一个。
4. 获取类的结构信息
4.1 获取构造方法
java
import java.lang.reflect.Constructor;
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
Class clazz = Student.class;
// 获取所有public构造方法
Constructor[] constructors = clazz.getConstructors();
// 获取所有构造方法(包括private)
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
// 获取指定参数的public构造方法
Constructor c1 = clazz.getConstructor(String.class, int.class);
// 获取指定参数的构造方法(包括private)
Constructor c2 = clazz.getDeclaredConstructor(String.class);
//获取构造方法的修饰符号
int modefiy = c2.getModifiers();
//获取所有参数
Parameter[] parameters = c2.getParameters();
// 通过构造方法创建对象
Student s = (Student) c1.newInstance("张三", 18);
// 如果是private构造方法,需要先设置可访问
c2.setAccessible(true);
Student s2 = (Student) c2.newInstance("李四");
}
}
4.2 获取成员变量
java
import java.lang.reflect.Field;
public class FieldDemo {
public static void main(String[] args) throws Exception {
Class clazz = Student.class;
// 获取所有public成员变量
Field[] fields = clazz.getFields();
// 获取所有成员变量(包括private)
Field[] declaredFields = clazz.getDeclaredFields();
// 获取指定的public成员变量
Field field1 = clazz.getField("name");
// 获取指定的成员变量(包括private)
Field field2 = clazz.getDeclaredField("age");
//获取成员修饰符
int modifiy = field2.getModifiers();
//获取名字
String name = field2.getName();
//获取数据类型
Class<?> type = field2.getType();
// 操作成员变量
Student s = new Student("张三", 18);
// 获取值
Object name = field1.get(s);
// 设置值
field1.set(s, "李四");
// private变量需要先设置可访问
field2.setAccessible(true);
field2.set(s, 20); // 给age赋值
}
}
4.3 获取成员方法
java
import java.lang.reflect.Method;
public class MethodDemo {
public static void main(String[] args) throws Exception {
Class clazz = Student.class;
// 获取所有public方法(包括父类的)
Method[] methods = clazz.getMethods();
// 获取所有方法(包括private,不包括父类)
Method[] declaredMethods = clazz.getDeclaredMethods();
// 获取指定的public方法
Method method1 = clazz.getMethod("study", String.class);
//获取方法修饰符
int modefiers = method1.getModifiers();
//获取方法名字
String name = method1.getName();
//获取所有参数
Parameter[] parameters = method1.getParameters();
// 获取指定的方法(包括private)
Method method2 = clazz.getDeclaredMethod("privateMethod");
//获取方法抛出的异常
Class[] = exceptions = method1.getExceptionTypes();
// 调用方法
Student s = new Student("张三", 18);
// 调用public方法
Object result = method1.invoke(s, "语文"); // 相当于 s.study("语文")
// 调用private方法
method2.setAccessible(true);
method2.invoke(s);
// 调用静态方法(不需要传对象)
Method staticMethod = clazz.getMethod("staticMethod");
staticMethod.invoke(null);
}
}
4.4 获取其他信息
java
public class OtherInfoDemo {
public static void main(String[] args) {
Class clazz = Student.class;
// 获取类名
String className = clazz.getName(); // 全类名:com.example.Student
String simpleName = clazz.getSimpleName(); // 简单类名:Student
// 获取包名
Package pkg = clazz.getPackage();
String pkgName = pkg.getName(); // com.example
// 获取父类
Class superClass = clazz.getSuperclass(); // Person.class
// 获取实现的接口
Class[] interfaces = clazz.getInterfaces(); // [Comparable.class, Serializable.class]
// 判断类型
boolean isArray = clazz.isArray(); // 是否是数组
boolean isInterface = clazz.isInterface(); // 是否是接口
boolean isEnum = clazz.isEnum(); // 是否是枚举
}
}
5. 反射常用API总结
5.1 Class类的方法
| 方法 | 说明 |
|---|---|
Class.forName(String) |
获取Class对象 |
newInstance() |
调用无参构造创建对象(已过时) |
getConstructor(Class...) |
获取public构造方法 |
getDeclaredConstructor(Class...) |
获取指定构造方法 |
getMethod(String, Class...) |
获取public方法 |
getDeclaredMethod(String, Class...) |
获取指定方法 |
getField(String) |
获取public成员变量 |
getDeclaredField(String) |
获取指定成员变量 |
getName() |
获取全类名 |
getSuperclass() |
获取父类 |
5.2 Constructor类的方法
| 方法 | 说明 |
|---|---|
newInstance(Object...) |
创建对象 |
setAccessible(boolean) |
设置可访问(突破private) |
5.3 Method类的方法
| 方法 | 说明 |
|---|---|
invoke(Object, Object...) |
调用方法(传对象和参数) |
setAccessible(boolean) |
设置可访问 |
getName() |
获取方法名 |
5.4 Field类的方法
| 方法 | 说明 |
|---|---|
get(Object) |
获取成员变量值 |
set(Object, Object) |
设置成员变量值 |
setAccessible(boolean) |
设置可访问 |
getType() |
获取变量类型 |
6. 反射的应用场景
| 场景 | 说明 |
|---|---|
| 框架开发 | Spring、MyBatis通过反射创建对象、注入属性 |
| 配置文件驱动 | 从配置文件读取类名,动态创建 |
| 注解处理 | 读取注解信息,执行相应逻辑 |
| 调试工具 | 查看任意对象的结构 |
7. 反射的优缺点
| 优点 | 缺点 |
|---|---|
| 动态性,运行时决定行为 | 性能较低(比直接调用慢) |
| 框架的基础 | 破坏封装(private可访问) |
| 高度灵活性 | 代码可读性下降 |
| 解耦 | 安全性降低 |
8. 易错点总结
-
Class.forName()要写全类名 :包含包名,如
com.example.Student -
newInstance()已过时 :Java 9后用
getDeclaredConstructor().newInstance() -
private成员需要setAccessible(true) :否则会报
IllegalAccessException -
invoke静态方法时对象参数传null :
method.invoke(null, 参数) -
基本类型反射要用Type :
int.class而不是Integer.class -
性能问题:高频调用的代码避免使用反射,或使用缓存
9. 对比总结
| 维度 | 正常调用 | 反射调用 |
|---|---|---|
| 时机 | 编译时 | 运行时 |
| 性能 | 快 | 慢 |
| 灵活性 | 低 | 高 |
| 封装性 | 遵守 | 可破坏 |
| 代码安全 | 高 | 低 |
| 适用场景 | 业务代码 | 框架代码 |