【从0开始学习Java | 第22篇】反射
文章目录
- Java反射:从基础到框架应用的实战指南
- 一、反射介绍
- 1. 什么是反射?
- 2. 为什么需要反射?
- 二、反射的核心:Class类
- 1. 获取Class对象的三种方式
- 方式1:通过 Class.forName(全类名)(运行时动态获取)
- 方式2:通过 类名.class(编译期已知类)
- 方式3:通过 对象.getClass()(已有实例)
- 三、反射实战:操作类的结构
- 1. 利用反射获取构造方法(Constuctor)
- 使用的方法
- 代码示例
- 2. 利用反射获取成员变量(Field)
- 使用的方法
- 代码示例
- 3. 调用方法(Method)
- 使用的方法
- 代码示例
- 四、反射的应用场景
- 五、反射的优缺点
- 优点:
- 缺点:
Java反射:从基础到框架应用的实战指南
一、反射介绍
1. 什么是反射?
简单来说,反射是Java提供的一种能力:允许程序在运行时获取类的详细信息(如属性、方法、构造器等),并动态操作这些信息。
正常情况下,我们使用类的流程是“编译期确定类 → 实例化 → 调用方法”,比如:
User user = new User(); // 编译期已知User类
user.setName("张三");
而反射则是“运行时获取类信息 → 动态操作”,即使编译期不知道具体类名,也能通过字符串(如配置文件中的类路径)完成对象创建和方法调用。
2. 为什么需要反射?
举个实际开发中的例子:假设你需要开发一个工具,根据用户配置的类名创建对象。如果没有反射,只能硬编码判断:
// 配置文件中读取的类名
String className = "com.example.Student"; // 不使用反射:必须提前知道所有可能的类,扩展性极差
if (className.equals("com.example.User")) {return new User();
} else if (className.equals("com.example.Student")) {return new Student();
} else {// 新增类时必须修改代码
}
而有了反射,只需两行代码即可动态处理任意类:
Class<?> clazz = Class.forName(className); // 运行时加载类
Object obj = clazz.newInstance(); // 动态创建实例
这就是反射的核心价值:摆脱编译期的类型依赖,让程序更灵活、更具扩展性。
二、反射的核心:Class类
反射的所有操作都围绕java.lang.Class
类展开。每个类被JVM加载后,都会生成一个唯一的Class
对象,它包含了该类的所有信息(属性、方法、构造器等)。可以说,Class
对象是反射的“入口”。
1. 获取Class对象的三种方式
要使用反射,第一步是获取目标类的Class
对象,常用三种方式:
方式1:通过 Class.forName(全类名)(运行时动态获取)
最常用的方式,通过类的全限定名(包名+类名)动态加载,适合从配置文件或数据库中读取类名的场景:
// 需处理ClassNotFoundException(类不存在时抛出)
Class<?> clazz = Class.forName("com.example.User");
方式2:通过 类名.class(编译期已知类)
如果编译期就知道具体类,直接通过类名.class
获取,无需处理异常:
Class<User> userClass = User.class;
Class<String> stringClass = String.class;
方式3:通过 对象.getClass()(已有实例)
如果已有对象实例,调用其getClass()
方法:
User user = new User();
Class<?> clazz = user.getClass(); // 此时clazz即为User类的Class对象
注意:
Class.forName()
会触发类的初始化(执行静态代码块),而后两种方式仅加载类不初始化。
三、反射实战:操作类的结构
我们以一个Student
类为例,演示如何通过反射操作构造器、属性和方法:
import java.io.IOException;public class Student {private String name;private int age;public String gender;public Student() {}public Student(String name) {this.name = name;}protected Student(int age) {this.age = age;}private Student(String name, int age) {this.name = name;this.age = age;}public Student(String name, int age, String gender) {this.name = name;this.age = age;this.gender = gender;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void sleep(){System.out.println("睡觉");}private void eat(String something) throws IllegalAccessError , IOException {System.out.println("在吃"+something);}}
1. 利用反射获取构造方法(Constuctor)
通过反射调用构造器创建对象,支持无参和有参构造,包括私有构造器。
使用的方法
代码示例
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;public class MyReflect_Constructor {public static void main(String []args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {// 1.获取class字节码文件对象Class clazz = Class. forName("Student");// 2.获取所有公共构造方法Constructor[] cons = clazz.getConstructors();for(Constructor con : cons){System.out.println(con);}System.out.println("==============");// 获取所有构造方法,包括私有Constructor[]cons2 = clazz.getDeclaredConstructors();for(Constructor con : cons2){System.out.println(con);}System.out.println("==============");Constructor con3 = clazz.getConstructor();System.out.println(con3);Constructor con4 = clazz.getDeclaredConstructor(int.class);System.out.println(con4);Constructor con5 = clazz.getConstructor(String.class);System.out.println(con5);Constructor con6 = clazz.getDeclaredConstructor(String.class,int.class);System.out.println(con6);// 获取权限修饰符int modifiers = con6.getModifiers();System.out.println(modifiers);// 获取对应构造方法的所有参数Parameter[] parameters = con6.getParameters();for(Parameter parameter :parameters){System.out.println(parameter);}// 暴力反射:临时取消权限的校验 --> 利用私有构造方法创建对象con6.setAccessible(true);Student stu = (Student)con6.newInstance("林七夜",18);System.out.println(stu);}
}
运行结果:
public Student(java.lang.String)
public Student()
==============
private Student(java.lang.String,int)
protected Student(int)
public Student(java.lang.String)
public Student()
==============
public Student()
protected Student(int)
public Student(java.lang.String)
private Student(java.lang.String,int)
2
java.lang.String arg0
int arg1
Student@4554617c
方法区别:
getConstructor()
只能获取public构造器,getDeclaredConstructor()
可获取所有权限的构造器。
2. 利用反射获取成员变量(Field)
反射可以获取类的所有属性(包括私有),并读写其值。
使用的方法
代码示例
import java.lang.reflect.Field;public class MyReflect_Field {public static void main(String []args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {// 1.获取class字节码文件的对象Class clazz = Class.forName("Student");// 2.获取成员变量// 公共的Field[] fields = clazz.getFields();for (Field field : fields) {System.out.println(field);}System.out.println("----------");// 所有的Field[] fields2 = clazz.getDeclaredFields();for (Field field : fields2) {System.out.println(field);}System.out.println("=========");// 3.获取单个成员变量// 私有Field name = clazz.getDeclaredField("name");System.out.println(name);System.out.println("---------");//公共Field gender = clazz.getField("gender");System.out.println(gender);// 4.获取权限修饰符int modifiers = name.getModifiers();System.out.println(modifiers);// 5.获取成员变量的名字String n = name.getName();System.out.println(n);// 6.获取成员变量的数据类型Class<?> type = name.getType();System.out.println(type);// 7.获取成员变量记录的值Student s = new Student("张三",18,"男");name.setAccessible(true);String value = (String) name.get(s);System.out.println(value);// 8.修改对象里面的值name.set(s,"李四");System.out.println(s);}
}
3. 调用方法(Method)
反射可以调用类的任意方法(包括私有方法),支持传递参数。
使用的方法
代码示例
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;public class MyReflect_Method {public static void main(String []args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {// 1.获取class字节码文件对象Class clazz = Class.forName("Student");// 2.获取里面所有的方法对象(包含父类中的所有公共方法)Method[] methods = clazz.getMethods();for (Method method : methods) {System.out.println(method);}System.out.println("-------------");// 3. 获取子类的所有方法(不包括父类的,但包括本类的私有方法)Method[] methods2 = clazz.getDeclaredMethods();for (Method method : methods2) {System.out.println(method);}// 4.获取单一指定方法Method m = clazz.getDeclaredMethod("eat",String.class);System.out.println(m);// 5.获取方法的修饰符int modifiers = m.getModifiers();System.out.println(modifiers);// 6.获取方法的名字System.out.println(m.getName());// 7.获取方法的形参Parameter[] parameters = m.getParameters();for (Parameter parameter : parameters) {System.out.println(parameter);}// 8.获取方法抛出的异常Class[] exceptionTypes = m.getExceptionTypes();for (Class exceptionType : exceptionTypes) {System.out.println(exceptionType);}// 9.Method类中用于创建对象的方法 (重点)Student s = new Student();m.setAccessible(true);// 参数一s:表示方法的调用者// 参数二"汉堡包":表示在调用方法是传递的实际参数m.invoke(s,"汉堡包");}
}
方法区别:
getMethod()
获取public方法,getDeclaredMethod()
获取所有权限的方法;invoke()
的第一个参数是实例(静态方法传null
),后续参数是方法的实际参数。
四、反射的应用场景
反射的灵活性使其成为框架设计的核心,但日常业务开发中需谨慎使用(避免过度设计)。常见应用场景包括:
- 框架的IOC容器:如Spring通过反射根据配置文件创建Bean,实现“控制反转”;
- ORM框架:如MyBatis通过反射将数据库查询结果映射到Java对象的属性;
- 动态代理:AOP的实现依赖反射调用目标方法(如事务增强、日志记录);
- 注解解析:自定义注解(如
@Controller
、@RequestMapping
)的生效需要反射扫描并处理; - 序列化/反序列化:JSON工具(如Jackson)通过反射将JSON字符串转换为Java对象。
五、反射的优缺点
优点:
- 动态性:运行时操作类,适应灵活配置场景(如通过配置文件切换实现类);
- 解耦:框架与业务类通过反射交互,无需硬编码依赖,降低耦合度;
- 通用性:一套反射代码可处理任意类,提升工具类的复用性。
缺点:
- 性能损耗:反射需要解析字节码,调用效率比直接调用低(但JVM已优化,非高频场景可忽略);
- 安全风险:
setAccessible(true)
会绕过访问权限检查,可能破坏类的封装性; - 可读性差:反射代码较抽象,调试和维护成本高于直接调用。