# Java 反射
反射定义&为什么要学习反射
反射定义
反射(Reflection)指在编译的时候不需要确定数据的类型,而是延迟到运行时获取类的信息(如类名、方法名、字段名)、调用类的方法、访问或修改类的字段,而无需在编译时确定这些信息,反射使Java这种静态编译型的语言具有了动态性。
反射具有看透类的能力,类的信息在反射面前都是透明的(包括private的属性和方法都是可以调用)
Java 的反射机制主要由 java.lang.reflect 包提供支持,包括 Class 类、Method 类、Field 类等。通过这些类,可以动态地加载类、创建类实例、调用类的方法、访问和修改类的字段等操作。
为什么要学习反射
框架:反射、泛型、注解
1、扩展性和灵活性:通过反射机制,可以实现动态加载类、调用方法、访问字段等操作,从而使程序具有更强的扩展性和灵活性。这在开发框架、插件系统或通用库时非常有用。
2、框架设计:许多 Java 框架(如Spring框架)都广泛使用了反射机制,通过反射可以实现依赖注入、AOP编程等功能。
3、框架源码阅读:许多开源框架和库中广泛使用了反射,学习反射可以帮助更好地理解和阅读这些框架的源码。
4、编写通用工具类:通过反射可以编写一些通用的工具类,比如序列化/反序列化工具、注解处理器、配置文件解析器等。
虽然反射机制有很多优点,但也需要注意反射可能带来的性能开销和安全隐患。因此,在使用反射时需要谨慎考虑,并避免滥用。总的来说,学习反射机制有助于提高 Java 程序员的编程技能和应用开发能力。
反射的缺点
尽管反射机制提供了很大的灵活性和扩展性,但也存在一些缺点和不足之处,包括:
1、性能开销:使用反射会导致性能开销较大,因为在运行时需要动态地解析类结构、查找方法或字段等,比直接调用方法要慢很多。这对性能要求高的应用可能不太适用。
2、编译时检查失效:由于反射是在运行时进行的,因此编译器无法对反射代码进行类型检查,容易引入类型错误,造成运行时异常。let v;
3、安全性问题:通过反射可以访问和修改类的私有字段和方法,这可能会绕过类的封装性,造成安全隐患,特别是在安全敏感的应用中需要格外小心。
4、可读性和维护性差:使用反射的代码通常比直接调用更难理解和维护,因为它隐藏了类的结构和行为,使得代码更加晦涩难懂。
5、性能优化困难:由于反射是在运行时动态进行的,因此很难进行静态分析和优化,使得程序的性能优化变得更加困难。
6、不利于代码重构:使用反射的代码通常依赖于类的具体实现细节,一旦类结构发生变化,可能导致反射代码出错,不利于代码的重构和演化。
综上所述,虽然反射机制为 Java 程序员提供了很多便利,但在实际应用中需要谨慎使用,权衡利弊。在性能要求高、安全性要求严格或代码可维护性重要的情况下,建议尽量避免过度依赖反射机制,寻找其他更合适的解决方案。

反射中涉及到的4个核心类
- Class 代表加载到内存的
.class字节码文件,一个类在内存中只有一份Class对象,是所有反射操作的前提。 - Field 代表类的成员属性,通过反射实现属性读写操作;前提:获取Field对象。
- Method 代表类的成员方法,通过反射调用方法;前提:获取Method对象。
- Constructor 代表类的构造方法,通过反射创建对象;前提:获取Constructor对象。

获取Class类对象的三种方式
//方式1:通过对象实例获取
Student student = new Student();
Class c1 = student.getClass();
//方式2:类名.class(编译期即可获取)
Class c2 = Student.class;
//方式3:静态方法 Class.forName("全类名"),运行时加载(框架最常用)
//全类名:com.study.text1.Student,经常配置在properties配置文件
Class c3 = Class.forName("com.study.text1.Student");
反射创建对象
操纵无参数构造方法(两种方式)
public static void main(String[] args) throws Exception{
//1. 获取Class对象
Class c = Class.forName("com.study.text1.Student");
//方式1:直接调用 newInstance() 【调用默认无参构造】
Object o = c.newInstance();
System.out.println(o instanceof Student);
//方式2:先获取Constructor构造器对象,再创建实例
Constructor constructor1 = c.getConstructor();
Object o1 = constructor1.newInstance();
System.out.println(o1 instanceof Student);
//等价普通代码:Student student = new Student();
}
操纵有参构造方法创建对象
public static void main(String[] args) throws Exception{
Class c = Class.forName("com.study.text1.Student");
//获取带两个参数的构造器,参数填写对应类型class
Constructor constructor2 = c.getConstructor(int.class,String.class);
//传入实参创建对象
Object o2 = constructor2.newInstance(12,"zs");
System.out.println(o2.toString());
//等价普通代码:Student student = new Student(12,"zs");
}
反射操纵对象属性
Class c = Class.forName("com.study.text1.Student");
Object o = c.newInstance();
// getField():只能获取 public 修饰的属性
Field name1 = c.getField("name");
// getDeclaredField():可以获取任意权限(private/protected/public)属性
Field name = c.getDeclaredField("name");
//获取全部对象
Field[] fields = clazz1.getDeclaredFields();
// 暴力访问,解除private权限限制
name.setAccessible(true);
// 设置属性值
name.set(o,"tom");
// 获取属性值
System.out.println(name.get(o));
反射调用对象方法
Class c = Class.forName("com.study.text1.Student");
Object o = c.newInstance();
// 参数1:方法名,后面依次填写方法形参的类型Class
Method method = c.getMethod("study", Object.class);
// invoke(对象,方法实参) 执行方法
method.invoke(o,"java");
完整案例:配置文件 + 反射解耦
config.properties 配置文件
class=com.study.text1.Student
method=study
Student实体类
public class Student {
private Integer age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public Student() {
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public void study(Object study) {
System.out.println("jiaoxue"+study);
}
}
测试主程序(读取配置、反射运行)
public static void main(String[] args) throws Exception {
//传统写法:硬编码,修改代码需要重新编译
// Student student = new Student();
// student.study();
Properties properties = new Properties();
properties.load(new FileInputStream("config.properties"));
//读取配置文件
String className = properties.getProperty("class");
String methodName = properties.getProperty("method");
//获取Class对象
Class c = Class.forName(className);
//反射创建对象(调用无参构造)
Object o = c.newInstance();
//获取目标方法
Method method = c.getMethod(methodName, Object.class);
//反射执行方法
method.invoke(o,"zs");
}
Java 反射综合实战案例
练习1:反射 + 配置文件 动态加载数据库实现类

需求:面向接口编程,只书写接口,不硬编码 new 具体数据库实现类;读取配置文件中的全类名,通过反射创建对象,切换数据库只需要修改配置文件即可。
1. 数据库顶层接口
public interface IDB {
void getConnection();
}
2. 各个数据库实现类
// MySQL 实现
public class MySql implements IDB {
@Override
public void getConnection() {
System.out.println("MySql.getConnection()");
}
}
// Oracle 实现
public class Oracle implements IDB {
@Override
public void getConnection() {
System.out.println("Oracle.getConnection()");
}
}
// SqlServer 实现
public class SqlServer implements IDB {
@Override
public void getConnection() {
System.out.println("SqlServer.getConnection()");
}
}
3. 配置文件 db1.properties
放在项目 resources 根目录,内容如下:
className=com.xxx.MySql
# 切换数据库只需修改全类名即可
# className=com.xxx.Oracle
# className=com.xxx.SqlServer
4. 反射加载主程序
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Properties;
public class DBDemo {
public static void main(String[] args) throws Exception {
// 1. 获取当前类的类加载器
ClassLoader classLoader = DBDemo.class.getClassLoader();
// 2. 通过类加载器读取resources下配置文件
InputStream inputStream = classLoader.getResourceAsStream("db1.properties");
Properties properties = new Properties();
properties.load(inputStream);
// 3. 读取配置中的实现类全限定类名
String className = properties.getProperty("className");
// 4. 反射获取Class对象
Class<?> clazz = Class.forName(className);
// 5. 获取无参构造器
Constructor<?> constructor = clazz.getConstructor();
// 6. 反射实例化对象,向上转型为接口
IDB db = (IDB) constructor.newInstance();
// 7. 调用方法
db.getConnection();
}
public void db(IDB db) {
db.getConnection();
}
}
特点
- 开闭原则:新增数据库实现类时,无需修改 Java 代码,只新增实现类 + 修改配置文件;
- 全程只依赖
IDB接口,实现解耦。
练习2:通用工具方法,将任意对象所有 String 属性中的字符 a 替换为 b
需求:编写通用方法 changeField(Object obj),传入任意对象,自动遍历所有成员变量,把所有 String 字段里的 a 替换成 b。
1. 实体类 Teacher
public class Teacher {
private String name = "android";
private int age = 18;
private String address = "qingdao";
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
2. 测试代码 + 反射工具方法
import org.junit.Test;
import java.lang.reflect.Field;
public class ReflectTest {
@Test
public void test1() throws IllegalAccessException {
Teacher teacher = new Teacher();
System.out.println(teacher);
changeField(teacher);
System.out.println(teacher);
}
/**
* 通用方法:任意对象,把所有String属性中的 a 替换成 b
*/
private void changeField(Object object) throws IllegalAccessException {
// 1. 获取对象运行时Class
Class<?> clazz = object.getClass();
// 2. 获取本类所有成员变量(包含private私有属性)
Field[] fields = clazz.getDeclaredFields();
// 3. 遍历所有字段
for (Field field : fields) {
// 只处理 String 类型属性
if (field.getType() == String.class) {
// 突破私有访问权限,否则会报 IllegalAccessException 访问拒绝异常
field.setAccessible(true);
// 获取属性原有值
String oldValue = (String) field.get(object);
if (oldValue != null && !oldValue.isEmpty()) {
// 替换字符
String newValue = oldValue.replace("a", "b");
// 重新赋值给对象属性
field.set(object, newValue);
}
}
}
}
}
运行结果
Teacher [name=android, age=18, address=qingdao]
Teacher [name=bndroid, age=18, address=qingdbo]
异常说明
java.lang.IllegalAccessException: 无法访问 private 修饰的成员
解决方案:
field.setAccessible(true); // 暴力反射,开启私有属性访问权限
核心思路
obj.getClass()获取运行时类型,实现任意对象通用;getDeclaredFields()拿到全部属性,包含私有;- 判断字段类型为
String才执行替换; setAccessible(true)解决私有属性无法读写的问题。
核心知识点总结
- 反射可以在运行时期获取类的结构、创建对象、读写私有成员变量;
setAccessible(true):暴力反射,破除 private 权限限制;- 配置文件 + 反射是工厂模式、Spring IOC 底层核心思想;
getDeclaredFields():获取当前类所有字段;getFields()只能拿到 public 字段。
关键总结
getConstructor()只能获取public构造器;私有构造需要使用getDeclaredConstructor()getMethod()获取public方法;getDeclaredMethod()获取所有权限方法- 访问私有属性/私有方法必须:
setAccessible(true)开启暴力反射 - 反射优势:解耦,只修改配置文件,不用修改Java代码、不用重启编译(Spring底层核心原理)