文章目录
反射是Java重要的基础特性,也是Spring、MyBatis等主流框架的底层核心支撑。本文聚焦反射基础知识,让读者快速掌握反射核心概念与基础使用方法。
一、反射机制概述
常规对象操作方式
在日常Java开发中,绝大多数代码都属于静态编码。程序在编译阶段就确定好需要使用的类、成员方法与成员变量,通过new关键字直接创建对象并完成业务调用,整个流程固定且可控。
java
// 编译阶段确定调用类与方法
User user = new User();
user.setName("张三");
反射的基本概念
反射是Java提供的动态编程机制,核心特性为:程序在运行阶段,可以动态获取目标类的结构信息、创建类实例、操作成员变量以及调用类中的方法。
两种编码方式核心区别:
- 常规正向操作:提前明确类结构,直接调用类的功能
- 反射动态操作:运行时解析类结构,动态完成功能调用
反射的核心原理
当JVM加载一个Java类时,会自动生成一个唯一的Class类对象,该对象完整封装了当前类的所有结构信息,包含构造方法、成员变量、成员方法等。所有反射操作都依托Class对象完成,Class对象是反射机制的唯一入口。
二、Class对象的三种获取方式
反射操作的前提是获取目标类的Class对象,Java提供三种基础获取方式,适配不同的开发场景。
类名.class
通过"类名.class"的方式静态获取Class对象,在编译阶段即可确定,执行效率最高,是静态编码场景的首选方式。
java
Class<User> clazz = User.class;
Class<String> stringClazz = String.class;
对象.getClass()
基于已实例化的对象获取Class对象,适用于已经创建对象实例的运行时场景。
java
User user = new User();
Class<? extends User> clazz = user.getClass();
Class.forName()
通过传入类的全限定名(包名+类名)字符串动态加载类,可在运行时灵活指定目标类,是框架底层动态加载类的常用方式。
java
// 需要填写目标类的完整包名与类名
Class<?> clazz = Class.forName("com.example.User");
三种方式适用场景总结
- 类名.class:静态获取、性能优异,适用于已知目标类的常规开发场景
- 对象.getClass():适用于已有对象实例,需要反向获取类信息的场景
- Class.forName():支持动态配置类路径,多用于框架配置、动态加载场景
三、反射基础实战案例
定义一个基础的User实体类,包含公有、私有成员变量与成员方法,作为反射操作的测试载体。
java
public class User {
// 公有成员变量
public String username;
// 私有成员变量
private Integer age;
// 无参构造方法
public User() {}
// 有参构造方法
public User(String username, Integer age) {
this.username = username;
this.age = age;
}
// 普通公有方法
public void sayHello() {
System.out.println("反射基础测试成功!");
}
// 私有方法
private void showAge() {
System.out.println("年龄:" + age);
}
// getter、setter方法
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
反射创建类实例
通过反射调用类的构造方法,可动态创建对象实例,支持无参构造和有参构造两种创建方式。
java
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 1. 获取目标类的Class对象
Class<User> clazz = User.class;
// 2. 通过无参构造创建对象
User user1 = clazz.getDeclaredConstructor().newInstance();
// 3. 通过有参构造创建对象
User user2 = clazz.getDeclaredConstructor(String.class, Integer.class)
.newInstance("小明", 18);
}
}
反射操作成员变量
利用反射可以读取和修改对象的成员变量,包含私有成员变量,核心方法用途区分如下:
- getField():仅获取类的公有成员变量
- getDeclaredField():获取类中所有权限的成员变量,包含私有变量
- setAccessible(true):解除私有成员的访问权限限制
java
public class ReflectFieldTest {
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
User user = clazz.getDeclaredConstructor().newInstance();
// 操作公有成员属性
clazz.getField("username").set(user, "小红");
System.out.println("用户名:" + clazz.getField("username").get(user));
// 操作私有成员属性
clazz.getDeclaredField("age").setAccessible(true);
clazz.getDeclaredField("age").set(user, 20);
System.out.println("年龄:" + clazz.getDeclaredField("age").get(user));
}
}
反射调用成员方法
通过反射可以动态调用类的公有方法与私有方法,核心方法用途区分如下:
- getMethod():获取类的公有成员方法
- getDeclaredMethod():获取类中所有权限的成员方法,包含私有方法
- invoke():执行获取到的成员方法
java
public class ReflectMethodTest {
public static void main(String[] args) throws Exception {
Class<User> clazz = User.class;
User user = clazz.getDeclaredConstructor().newInstance();
// 调用公有成员方法
clazz.getDeclaredMethod("sayHello").invoke(user);
// 调用私有成员方法
clazz.getDeclaredMethod("showAge").setAccessible(true);
clazz.getDeclaredMethod("showAge").invoke(user);
}
}
四、反射机制的优缺点
- 优点
- 灵活性较强:可在程序运行阶段动态解析类结构、操作类成员,突破编译阶段的代码固定限制
- 框架核心基础:是Spring、MyBatis等主流开源框架的底层实现核心,支撑框架的动态加载、属性注入等功能
- 缺点
- 运行性能较低:反射需要动态解析类结构、校验访问权限,相比直接调用代码,执行效率更低
- 破坏封装特性:可强制访问类的私有成员,打破面向对象的封装原则,存在一定的代码安全隐患
五、总结
- 反射的核心原理:依托Class对象,在程序运行时动态解析并操作类的全部资源;
- Class对象包含三种获取方式,静态开发优先使用类名.class方式,性能更优;
- 反射常用基础功能包含动态创建对象、操作成员变量、调用成员方法,访问私有成员需手动开启权限;
- 反射灵活性高但存在性能与封装性短板,常规业务开发不建议滥用,掌握基础用法即可满足日常开发与学习需求。