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

Java基础知识(十)

Java 反射机制详解

反射(Reflection)是 Java 语言的一个重要特性,它允许程序在运行时获取类的信息并操作类或对象的属性、方法等成员。通过反射,我们可以在编译时未知类信息的情况下,动态地创建对象、调用方法和访问属性。

1. 反射的基本概念

反射机制允许程序:

  • 在运行时获取任意类的字节码信息
  • 在运行时构造任意类的对象
  • 在运行时调用任意类的方法
  • 在运行时访问和修改任意类的属性

反射的核心是java.lang.Class类,它代表了一个类的字节码,并提供了获取类信息的方法。

2. 获取 Class 对象的三种方式

获取Class对象是使用反射的第一步,有三种常用方式:

// 方式1:通过类名.class获取
Class<?> clazz1 = String.class;// 方式2:通过对象的getClass()方法获取
String str = "Hello";
Class<?> clazz2 = str.getClass();// 方式3:通过Class.forName()方法获取(最常用,可动态加载类)
Class<?> clazz3 = Class.forName("java.lang.String");

3. 反射常用类

Java 反射主要涉及以下类(均位于java.lang.reflect包):

  • Class:代表类的字节码
  • Constructor:代表类的构造方法
  • Method:代表类的方法
  • Field:代表类的成员变量
  • Parameter:代表方法的参数(Java 8+)

4. 反射的基本操作

4.1 创建对象

通过反射创建对象有两种方式:

public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}// getter、setter和toString方法省略
}

// 获取Class对象
Class<?> personClass = Class.forName("com.example.Person");// 方式1:通过无参构造方法创建对象(要求类必须有无参构造)
Person person1 = (Person) personClass.newInstance();// 方式2:通过指定构造方法创建对象
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Person person2 = (Person) constructor.newInstance("Alice", 20);

4.2 获取和调用方法

// 获取所有公共方法(包括继承的)
Method[] methods = personClass.getMethods();// 获取类自身声明的所有方法(包括私有)
Method[] declaredMethods = personClass.getDeclaredMethods();// 获取指定公共方法
Method setNameMethod = personClass.getMethod("setName", String.class);// 获取指定私有方法
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
// 设置访问权限(关键)
privateMethod.setAccessible(true);// 调用方法
Person person = new Person();
setNameMethod.invoke(person, "Bob");  // 调用public方法
privateMethod.invoke(person);         // 调用private方法

4.3 获取和操作属性

// 获取所有公共属性(包括继承的)
Field[] fields = personClass.getFields();// 获取类自身声明的所有属性(包括私有)
Field[] declaredFields = personClass.getDeclaredFields();// 获取指定公共属性
Field publicField = personClass.getField("publicField");// 获取指定私有属性
Field nameField = personClass.getDeclaredField("name");
// 设置访问权限
nameField.setAccessible(true);// 操作属性
Person person = new Person();
nameField.set(person, "Charlie");  // 设置私有属性值
String name = (String) nameField.get(person);  // 获取私有属性值

5. 反射的应用场景

  1. 框架开发:Spring、MyBatis 等框架大量使用反射实现对象创建和依赖注入
  2. 动态代理:AOP 实现的基础
  3. 序列化与反序列化:将对象转换为字节流或从字节流恢复对象
  4. 注解处理:通过反射获取注解信息并处理
  5. ORM 框架:实现对象与数据库表的映射

6. 反射示例:简易 ORM 框架

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;public class SimpleORM {// 保存对象到数据库public static void save(Object obj) throws Exception {Class<?> clazz = obj.getClass();StringBuilder sql = new StringBuilder("INSERT INTO ");sql.append(clazz.getSimpleName().toLowerCase()).append(" (");Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {sql.append(field.getName()).append(",");}// 移除最后一个逗号sql.deleteCharAt(sql.length() - 1);sql.append(") VALUES (");for (int i = 0; i < fields.length; i++) {sql.append("?,");}// 移除最后一个逗号sql.deleteCharAt(sql.length() - 1);sql.append(")");// 实际开发中需要获取数据库连接Connection conn = null; // 获取连接的代码省略PreparedStatement pstmt = conn.prepareStatement(sql.toString());for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);pstmt.setObject(i + 1, fields[i].get(obj));}pstmt.executeUpdate();// 关闭资源的代码省略}
}

7. 反射的优缺点

优点

  • 提高程序灵活性和扩展性
  • 实现动态加载类和创建对象
  • 框架开发的基础

缺点

  • 性能开销较大:反射操作比直接调用慢
  • 破坏封装性:可以访问私有成员
  • 代码可读性降低:反射代码相对复杂
  • 安全问题:可能绕过安全检查

8. 反射性能优化

  1. 缓存 Class 对象:避免多次获取 Class 对象
  2. 设置 setAccessible (true):关闭安全检查,提高性能
  3. 使用 MethodHandle(Java 7+):比反射性能更好
  4. 避免在循环中使用反射:将反射操作移到循环外

// 缓存Method示例
public class ReflectionCache {private static Map<String, Method> methodCache = new HashMap<>();public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {String key = clazz.getName() + "#" + methodName + Arrays.toString(parameterTypes);if (!methodCache.containsKey(key)) {Method method = clazz.getMethod(methodName, parameterTypes);method.setAccessible(true);methodCache.put(key, method);}return methodCache.get(key);}
}

9. 注意事项

  1. 反射会破坏封装,应谨慎使用
  2. 反射可能导致代码难以理解和维护
  3. 某些安全管理器可能会禁止反射操作
  4. 对于 final 字段,反射修改可能不会生效(取决于 JVM 实现)
  5. 使用反射时应处理各种可能的异常

总结

反射是 Java 中一个强大的特性,它允许程序在运行时操作类和对象的成员,为框架开发提供了极大的灵活性。然而,反射也有其缺点,如性能开销和封装性问题。在实际开发中,应根据具体需求权衡使用,并注意性能优化。

反射的核心是Class类,通过它可以获取类的各种信息,并借助ConstructorMethodField等类实现对类成员的操作。合理使用反射可以极大地提高代码的灵活性和扩展性。

整理成笔记,尽量详细

Java 反射实操笔记

一、反射核心 API 梳理

1.1 类对象获取方式

获取方式语法适用场景
Class.forName()Class clazz = Class.forName("全类名");编译时未知类,需动态加载(如读取配置文件)
类名.classClass clazz = 类名.class;编译时已知类,直接通过类名获取
对象.getClass()Class clazz = 对象名.getClass();已有对象实例,通过对象反推类信息

1.2 成员变量操作 API

方法访问权限范围功能描述
getDeclaredFields()类自身声明的所有成员变量(包括 private、protected、public)获取类的所有成员变量数组
getDeclaredField(String name)类自身声明的指定成员变量(任意权限)获取单个指定名称的成员变量
getFields()类及父类的 public 成员变量仅获取公共成员变量数组
getField(String name)类及父类的指定 public 成员变量获取单个指定名称的公共成员变量
set(Object obj, Object value)无(需配合setAccessible(true)给指定对象的成员变量赋值
get(Object obj)无(需配合setAccessible(true)获取指定对象的成员变量值
setAccessible(true)暴力解除访问权限检查让 private 成员变量可被操作(核心!)

1.3 成员方法操作 API

方法访问权限范围功能描述
getDeclaredMethods()类自身声明的所有方法(任意权限)获取类的所有方法数组
getDeclaredMethod(String name, Class<?>... paramTypes)类自身声明的指定方法(任意权限)获取单个指定名称 + 参数类型的方法
getMethods()类及父类的 public 方法仅获取公共方法数组(含 Object 类方法)
getMethod(String name, Class<?>... paramTypes)类及父类的指定 public 方法获取单个指定名称 + 参数类型的公共方法
invoke(Object obj, Object... args)无(需配合setAccessible(true)执行指定对象的方法,args 为方法参数
getName()获取方法的名称(字符串形式)
setAccessible(true)暴力解除访问权限检查让 private 方法可被调用

1.4 构造方法操作 API

方法访问权限范围功能描述
getDeclaredConstructors()类自身声明的所有构造方法(任意权限)获取所有构造方法数组
getDeclaredConstructor(Class<?>... paramTypes)类自身声明的指定构造方法(任意权限)获取单个指定参数类型的构造方法(无参则传空)
getConstructors()类自身声明的 public 构造方法仅获取公共构造方法数组
getConstructor(Class<?>... paramTypes)类自身声明的指定 public 构造方法获取单个指定参数类型的公共构造方法
newInstance(Object... args)无(需配合setAccessible(true)通过构造方法创建对象实例,args 为构造参数

1.5 类信息获取 API

方法返回值功能描述
getName()String获取类的全名称(包名 + 类名,如com.qcby.reflect.Person
getSimpleName()String获取类的简单名称(仅类名,如Person

二、配置文件与核心类(基于文档 2、4)

2.1 配置文件:refconfig.properties

用于存储反射所需的类名和方法名,实现 “配置化加载”,避免硬编码。

# 全类名(包名+类名)
reflect.className=com.qcby.reflect.Person
# 要调用的方法名
reflect.methodName=getAge

2.2 目标类:Person.java

反射操作的目标类,包含不同权限的成员变量、方法和构造方法。

package com.qcby.reflect;public class Person {// 私有成员变量private String name;private int age;// 公共成员变量public String from;public String height;// 公共getter/setter(访问私有变量)public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {System.out.println("getAge方法执行");return age;}public void setAge(int age) {this.age = age;}// 私有方法private void run() {System.out.println("run方法执行");}// 构造方法(无参、1参、2参,均为public)public Person() {}public Person(String name) {this.name = name;}public Person(String name, int age) {this.name = name;this.age = age;}// 重写toString,便于打印对象信息@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + " , from = "+from+" , height = "+height+"]";}
}

三、反射实操案例(基于文档 3)

3.1 步骤 1:获取类对象

三种获取Person类对象的方式,文档 3 中均有演示:

// 方式1:通过Class.forName(推荐,支持动态加载)
Class pclass1 = Class.forName("com.qcby.reflect.Person");// 方式2:通过类名.class(编译时已知类)
Class pclass2 = Person.class;// 方式3:通过对象.getClass(已有对象实例)
Person person = new Person();
Class pclass3 = person.getClass();

3.2 步骤 2:操作成员变量

2.1 获取成员变量

// 1. 获取类自身所有成员变量(包括private)
Field[] allFields = pclass.getDeclaredFields();
for (Field item : allFields) {System.out.println(item); // 输出:private java.lang.String com.qcby.reflect.Person.name、private int com.qcby.reflect.Person.age 等
}// 2. 获取单个指定私有成员变量(如age)
Field ageField = pclass.getDeclaredField("age");
System.out.println(ageField); // 输出:private int com.qcby.reflect.Person.age// 3. 获取所有public成员变量(包括父类,此处父类为Object,无额外public变量)
Field[] publicFields = pclass.getFields();
for (Field item : publicFields) {System.out.println(item); // 输出:public java.lang.String com.qcby.reflect.Person.from、public java.lang.String com.qcby.reflect.Person.height
}// 4. 获取单个指定public成员变量(如from)
Field fromField = pclass.getField("from");
System.out.println(fromField); // 输出:public java.lang.String com.qcby.reflect.Person.from
2.2 赋值与取值

// 1. 给public变量赋值(直接操作,无需暴力反射)
Person person = new Person();
fromField.set(person, "中国"); // 给person的from变量赋值为“中国”// 2. 给private变量赋值(需先暴力解除权限)
ageField.setAccessible(true); // 关键:解除private权限检查
ageField.set(person, 20); // 给person的age变量赋值为20// 3. 获取变量值
System.out.println(ageField.get(person)); // 输出:20(获取private变量age的值)
System.out.println(fromField.get(person)); // 输出:中国(获取public变量from的值)
System.out.println(person); // 输出:Person [name=null, age=20 , from = 中国 , height = null](通过toString验证)

3.3 步骤 3:操作成员方法

3.1 获取成员方法

// 1. 获取类自身所有方法(包括private)
Method[] allMethods = pclass.getDeclaredMethods();
for (Method item : allMethods) {System.out.println(item); // 输出:private void com.qcby.reflect.Person.run()、public int com.qcby.reflect.Person.getAge() 等
}// 2. 获取单个指定private方法(如run,无参数)
Method runMethod = pclass.getDeclaredMethod("run");
System.out.println(runMethod); // 输出:private void com.qcby.reflect.Person.run()// 3. 获取所有public方法(含父类Object的方法,如toString、equals)
Method[] publicMethods = pclass.getMethods();
for (Method item : publicMethods) {System.out.println(item); // 输出:public int com.qcby.reflect.Person.getAge()、public java.lang.String com.qcby.reflect.Person.toString() 等System.out.println(item.getName()); // 输出方法名,如getAge、toString
}// 4. 获取单个指定public方法(如getAge,无参数)
Method getAgeMethod = pclass.getMethod("getAge");
System.out.println(getAgeMethod); // 输出:public int com.qcby.reflect.Person.getAge()
3.2 调用方法

Person person = new Person();
// 1. 调用public方法(直接调用)
getAgeMethod.invoke(person); // 输出:getAge方法执行(方法内部打印)// 2. 调用private方法(需先暴力解除权限)
runMethod.setAccessible(true); // 关键:解除private权限检查
runMethod.invoke(person); // 输出:run方法执行(方法内部打印)

3.4 步骤 4:操作构造方法

4.1 获取构造方法

// 1. 获取类自身所有构造方法(包括非public,此处Person构造均为public)
Constructor[] allConstructors = pclass.getDeclaredConstructors();
for (Constructor item : allConstructors) {System.out.println(item); // 输出:public com.qcby.reflect.Person()、public com.qcby.reflect.Person(java.lang.String)、public com.qcby.reflect.Person(java.lang.String,int)
}// 2. 获取单个指定构造方法(无参)
Constructor noArgConstructor = pclass.getDeclaredConstructor();
System.out.println(noArgConstructor); // 输出:public com.qcby.reflect.Person()// 3. 获取单个指定构造方法(2参:String + int)
Constructor twoArgConstructor = pclass.getDeclaredConstructor(String.class, int.class);
System.out.println(twoArgConstructor); // 输出:public com.qcby.reflect.Person(java.lang.String,int)// 4. 获取所有public构造方法(与getDeclaredConstructors()结果一致,因Person构造均为public)
Constructor[] publicConstructors = pclass.getConstructors();
for (Constructor item : publicConstructors) {System.out.println(item);
}// 5. 获取单个指定public构造方法(1参:String)
Constructor oneArgConstructor = pclass.getConstructor(String.class);
System.out.println(oneArgConstructor); // 输出:public com.qcby.reflect.Person(java.lang.String)
4.2 创建对象实例

// 1. 通过无参构造创建对象
Object person1 = noArgConstructor.newInstance();
System.out.println(person1); // 输出:Person [name=null, age=0 , from = null , height = null]// 2. 通过2参构造创建对象(需强转为Person类型)
Person person2 = (Person) twoArgConstructor.newInstance("lili", 20);
System.out.println(person2); // 输出:Person [name=lili, age=20 , from = null , height = null]
person2.getAge(); // 调用对象方法,输出:getAge方法执行

3.5 步骤 5:结合配置文件动态反射

通过Properties读取配置文件,动态获取类名和方法名,实现 “不修改代码,仅改配置即可切换反射目标”。

// 1. 创建Properties对象,用于读取配置文件
Properties properties = new Properties();// 2. 加载配置文件(通过类加载器获取输入流,路径为“包名/文件名”)
InputStream in = Person.class.getClassLoader().getResourceAsStream("com/qcby/reflect/refconfig.properties");
properties.load(in); // 加载配置文件内容// 3. 从配置文件中获取类名和方法名
String className = properties.getProperty("reflect.className"); // 取值:com.qcby.reflect.Person
String methodName = properties.getProperty("reflect.methodName"); // 取值:getAge// 4. 动态获取类对象
Class pclass = Class.forName(className);
System.out.println(pclass); // 输出:class com.qcby.reflect.Person// 5. 后续可继续动态操作(如获取方法、调用方法)
// 示例:获取所有成员变量
Field[] fields = pclass.getDeclaredFields();
for (Field item : fields) {System.out.println(item); // 输出Person的所有成员变量
}

四、关键注意事项

  1. setAccessible(true)的作用
    解除 Java 的访问权限检查,仅对getDeclaredXXX()获取的私有成员有效,getXXX()获取的公共成员无需此操作。
  2. 异常处理
    反射操作可能抛出ClassNotFoundException(类找不到)、NoSuchFieldException(字段找不到)、NoSuchMethodException(方法找不到)、IllegalAccessException(权限不足)等,需在方法上声明throws Exception或捕获处理。
  3. 类型强转
    通过newInstance()创建的对象默认是Object类型,需根据实际类强转为目标类型(如(Person) constructor.newInstance())。
  4. 性能问题
    反射操作比直接调用慢(需动态解析类信息),频繁操作时建议缓存ClassMethodField对象,减少重复获取。
http://www.xdnf.cn/news/19298.html

相关文章:

  • plantsimulation知识点 多条RGV驮一台工件图标显示顺序问题
  • C语言类型转换踩坑解决过程
  • 重叠IO模型
  • 深入理解 Linux 驱动中的 file_operations:从 C 语言函数指针到类比 C++ 虚函数表
  • 学习Python中Selenium模块的基本用法(11:弹窗处理)
  • Day18_【机器学习—交叉验证与网格搜索】
  • 【ROS2】ROS2 基础学习教程 、movelt学习
  • PostgreSQL 数据库灾备要点与举例说明**
  • Spring Data Redis 的使用方法
  • 电子战:多功能雷达工作模式识别
  • [光学原理与应用-339]:ZEMAX - Spot Diagram(点列图)是评估光学系统成像质量的核心工具,它通过几何光线追迹直观展示像差对成像的影响。
  • 模拟实现STL中的list容器
  • 行内元素块元素
  • Coze源码分析-API授权-添加新令牌-后端源码
  • mysql权限user表赋权操作修改
  • 【大语言模型 30】指令微调数据工程:高质量数据集构建
  • 计算机算术7-浮点基础知识
  • 面试tips--MyBatis--<where> where 1=1 的区别
  • Burgan Bank Türkiye 如何借助 Elastic 改造可观测性和安全性
  • 【LeetCode 热题 100】62. 不同路径——(解法四)组合数学
  • Scikit-learn Python机器学习 - Scikit-learn加载数据集
  • 49.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--Refit跨服务调用
  • Photoshop - Ps Camera Raw 滤镜
  • 爱普生L3255打印机故障记录
  • 算法(②排序算法)
  • 在word以及latex中引用zotero中的参考文献
  • JVM架构图是怎样的?
  • Python - 机器学习:从 “教电脑认东西” 到 “让机器自己学规律”
  • 第7.5节:awk语言 switch 语句
  • Kubernetes 部署与发布完全指南:从 Pod 到高级发布策略