当前位置: 首页 > web >正文

Java反射全解(八股)

Java反射机制(Reflection)简介

反射(Reflection)是 Java 的元编程特性,允许在运行时获取类的信息、访问属性、调用方法,甚至操作私有成员。它属于 java.lang.reflect 包的一部分。

反射功能主要通过 java.lang.Class 类及 java.lang.reflect 包中的类如 Method, Field, Constructor 等来实现。


Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。

![[Pasted image 20250421105248.png]]

🚀 反射的作用

  1. 运行时获取类的信息

    • 类名、字段、方法、构造器等。
  2. 动态创建对象

    • 无需显式调用构造器。
  3. 动态访问属性和方法

    • 无需在编译期确定。
  4. 修改或访问私有成员

    • 打破封装,修改 private 字段或调用 private 方法。
  5. 框架底层实现

    • Spring、MyBatis、Hibernate 等框架大量使用反射实现依赖注入、AOP 等功能。

反射的常见操作

📌 1️⃣ 获取 Class 对象

反射的入口是 Class 类,通过它可以访问类的元数据:

// 方式一:通过类名
Class<?> clazz1 = Person.class;// 方式二:通过对象
Person p = new Person();
Class<?> clazz2 = p.getClass();// 方式三:通过类的全限定名(常用)
Class<?> clazz3 = Class.forName("com.example.Person");// 创建对象
String className = "java.util.Date";
Class<?> cls = Class.forName(className);
Object obj = cls.newInstance();
System.out.println(obj.getClass().getName());

📌 区别:

  • Person.class → 编译时确定类型。

  • p.getClass() → 运行时获取对象的类型。

  • Class.forName() → 运行时动态加载类(支持全限定名)。


📌 2️⃣ 获取类的信息

获取类名

System.out.println(clazz1.getName());         // 类的全限定名:com.example.Person
System.out.println(clazz1.getSimpleName());   // 类名:Person

获取字段

Field[] fields = clazz1.getDeclaredFields();
for (Field field : fields) {System.out.println("字段名称:" + field.getName());System.out.println("字段类型:" + field.getType());
}

获取方法

Method[] methods = clazz1.getDeclaredMethods();
for (Method method : methods) {System.out.println("方法名:" + method.getName());System.out.println("返回类型:" + method.getReturnType());System.out.println("参数类型:" + Arrays.toString(method.getParameterTypes()));
}

获取构造器

Constructor<?>[] constructors = clazz1.getConstructors();
for (Constructor<?> constructor : constructors) {System.out.println("构造器参数:" + Arrays.toString(constructor.getParameterTypes()));
}

📌 3️⃣ 使用反射创建对象

调用无参构造器

Class<?> clazz = Class.forName("com.example.Person");
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(obj);

调用带参构造器

Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("Alice", 25);
System.out.println(obj);

📌 4️⃣ 动态访问属性

修改 public 字段

Field field = clazz.getField("name");
field.set(obj, "Bob");    // 修改字段值
System.out.println(field.get(obj));   // 获取字段值

修改 private 字段

Field field = clazz.getDeclaredField("age");
field.setAccessible(true);  // 暴力破解私有访问权限
field.set(obj, 30);
System.out.println(field.get(obj));

📌 5️⃣ 动态调用方法

调用 public 方法

Method method = clazz.getMethod("sayHello");
method.invoke(obj);   // 调用方法

调用 private 方法

Method method = clazz.getDeclaredMethod("secretMethod");
method.setAccessible(true);  // 暴力破解访问权限
method.invoke(obj);

📌 6️⃣ 判断注解

检测类上是否存在注解

if (clazz.isAnnotationPresent(MyAnnotation.class)) {System.out.println("类上存在 MyAnnotation 注解");
}

获取字段上的注解

Field field = clazz.getDeclaredField("name");
MyAnnotation annotation = field.getAnnotation(MyAnnotation.class);
if (annotation != null) {System.out.println("注解值:" + annotation.value());
}

反射的常见应用场景

🚀 1️⃣ 动态加载类(避免硬编码)

在开发中,有时类名、方法名在编译时无法确定,可以通过反射在运行时加载:

// 动态加载类
String className = "com.example.Person";
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
System.out.println(obj);

📌 应用场景

  • JDBC 动态加载数据库驱动:
Class.forName("com.mysql.cj.jdbc.Driver");

🚀 2️⃣ 框架底层实现

Spring 中的依赖注入

// 通过反射动态创建对象并注入属性
Object bean = clazz.getDeclaredConstructor().newInstance();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(bean, "Spring Injected Name");
System.out.println(field.get(bean));

MyBatis 映射数据库字段

// 动态映射数据库字段
String columnName = "username";
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj, resultSet.getString(columnName));

Java 的动态代理(Dynamic Proxy)机制

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class<?>[] { MyInterface.class },handler
);

JUnit 和 TestNG 等测试框架

反射允许框架扫描类,查找带有特定注解(如 @Test)的方法,并在运行时调用它们。

Method testMethod = testClass.getMethod("testSomething");
testMethod.invoke(testInstance);

反射的优缺点

优点缺点
动态加载类,灵活性强性能损耗较大
访问私有字段和方法不安全,可能破坏封装
框架底层常用代码可读性较差
动态生成和调用方法易出错,难以调试

反射的优化建议

  1. 缓存反射对象

    • 反射操作比直接调用慢,频繁使用反射时,建议将 ClassFieldMethod 等缓存起来。

    • 例如:

Map<String, Method> methodCache = new HashMap<>();
Method method = clazz.getMethod("getName");
methodCache.put("getName", method);
  1. 避免频繁使用反射

    • 尽量在启动时完成反射初始化,运行时减少反射调用。

    • 例如:Spring 将 Bean 实例化和依赖注入在启动时完成。


反射符合面向对象吗

一定程度上是支持 OOP 的

  • 反射提供了一种灵活的手段,实现框架化、解耦(如 Spring、JDBC、MyBatis 都 heavily 用反射)

  • 让程序具备一定的“自描述、自适应能力”,这在某种程度上是“高阶封装”

但它也打破了封装性

  • 能访问私有字段、方法,违背了封装和信息隐藏

  • 比如你不希望外部改你类的私有属性,但反射能做到

我认为反射本身并不是违反面向对象规范的,它是 Java 提供的一种底层机制,目的是为了增强语言的灵活性和扩展性。但它确实可能被滥用,从而打破封装、破坏安全性,所以在使用时要权衡设计目标和风险。在一些框架、IOC 场景中,反射是非常重要和合理的工具。
比如我们在用 Spring 的时候,反射帮助我们动态注入 Bean、实现 AOP,本质是为了更好地解耦和扩展,这是服务于面向对象设计目标的,所以合理使用反射,是在 OOP 的范围内的。

🚀 总结

  • 反射是 Java 元编程的重要特性,可以在运行时操作类和对象。

  • 在框架(如 Spring、MyBatis、Hibernate)中广泛应用。

  • 虽然反射灵活,但性能较低,频繁使用时需注意优化。

  • 常见操作包括:动态创建对象、访问私有字段、调用方法、处理注解等。

http://www.xdnf.cn/news/868.html

相关文章:

  • 文档处理控件Aspose.Words 教程:在 Word 中删除空白页完整指南
  • 2025年二级造价工程师备考要点分析
  • spark和hadoop的区别
  • 【C++游戏引擎开发】第19篇:Compute Shader实现Tile划分
  • 计组1.2.2——各个硬件的工作原理
  • 硬件工程师面试常见问题(4)
  • 操作系统期中复习
  • 车载软件架构 --- 二级boot设计说明需求规范
  • 序列号绑定的SD卡坏了怎么办?
  • AI驱动下的企业学习:人力资源视角下的范式重构与价值觉醒
  • Materials Studio(二)——无机分子建模
  • 当try遇见catch:前端异常捕获的边界与突围
  • ADB -> pull指令推送电脑文件到手机上
  • 24. git revert
  • [渗透测试]渗透测试靶场docker搭建 — —全集
  • 【Linux】轻量级命令解释器minishell
  • 计算机组成原理笔记(十九)——4.4定点乘法运算
  • CentOS 7进入救援模式——VirtualBox虚拟机
  • 深入解析Vue3响应式系统:从Proxy实现到依赖收集的核心原理
  • Kubernetes 创建 Jenkins 实现 CICD 配置指南
  • 目标检测中的损失函数(二) | BIoU RIoU α-IoU
  • k8s之 kube-prometheus监控
  • 6N60-ASEMI机器人功率器件专用6N60
  • RabbitMQ
  • 进程控制(linux)
  • Tailwind 武林奇谈:bg-blue-400 失效,如何重拾蓝衣神功?
  • CSS零基础入门笔记:狂神版
  • http 文件下载和上传服务
  • Android RK356X TVSettings USB调试开关
  • LabVIEW数据采集与传感系统