Java 反射核心 API 综合应用:从类信息解析到对象动态操作
·
概述
反射就是把java类中的各种成分映射成一个个的Java对象,反射原理是在与class对象


反射核心API
| 类名 | 作用 | 关键方法 |
|---|---|---|
Class |
表示运行时类或接口 | forName(), newInstance(), getDeclaredFields(), getDeclaredMethods(),getSuperclass(),getAnnotations() |
Field |
表示类的字段(成员变量) | get(), set(), setAccessible() |
Method |
表示类的方法 | invoke(), setAccessible() |
Constructor |
表示类的构造方法 | newInstance(), setAccessible() |
Modifier |
解析类和成员的修饰符 | isPublic(), isPrivate(), isStatic() |
获取成员的API对比: getXXX()与getDeclaredXXX()的区别
| 方法 | 是否包含非 public 成员 | 是否包含父类成员 | 适用场景 |
|---|---|---|---|
getFields() |
否(仅 public) | 是(父类 public) | 需访问父类公共字段时 |
getDeclaredFields() |
是(全权限) | 否 | 访问本类所有字段(含私有) |
getMethods() |
否(仅 public) | 是(父类 public) | 需调用父类公共方法时 |
getDeclaredMethods() |
是(全权限) | 否 | 调用本类所有方法(含私有) |
获取class对象方法
Class.forName(“全类名”);
适用于源代码阶段, 全类名->包名+类名
类名.class
适用于加载阶段,一般当作参数进行传递
对象.getClass();
适应于运行阶段,已经存在该对象的类
举例如下
反射获取构造方法(Constructor)

// 使用公有构造方法创建实例
Student student1 = publicConstructor.newInstance("Alice", 30); //创建学生信息

注意事项
访问权限
getConstructors()与getConstructor():只能获取public构造方法。如果一个类没有公共构造方法,这些方法将返回空数组或抛出异常。getDeclaredConstructors()与getDeclaredConstructor():能获取本类中声明的所有构造方法(包括private,protected, 默认权限)。这是访问私有构造方法的关键。- 访问非公有构造方法:通过
getDeclaredConstructor获取到非public的Constructor对象后,直接调用newInstance()会抛出IllegalAccessException。必须先调用constructor.setAccessible(true)来取消 Java 语言访问检查。 - 安全管理器 (SecurityManager):如果存在安全管理器,它可能会阻止你修改访问权限(调用
setAccessible(true)),并抛出SecurityException。
异常处理
NoSuchMethodException:当通过指定的参数类型列表找不到匹配的构造方法时抛出。确保参数类型、数量和顺序完全正确。IllegalAccessException:当没有权限访问构造方法时抛出(通常是因为没调用setAccessible(true)或安全管理器阻止)。InstantiationException:当试图通过抽象类、接口、数组类或没有无参构造方法的类(在特定情况下)的Class.newInstance()(已废弃)方法实例化对象时抛出。使用Constructor.newInstance()可以避免部分问题。InvocationTargetException:这是一个包装异常。如果构造方法本身在执行过程中抛出了异常(如参数验证失败),该异常会被包装在InvocationTargetException中。你需要调用e.getCause()来获取原始的异常信息,这是调试的关键。IllegalArgumentException:当传入的initargs实参与声明的形参类型不兼容时抛出。
//健壮的异常处理示例:
try {
// 1. 获取构造方法对象
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
// 2. 解除访问限制(如果需要)
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
// 3. 创建实例
Object instance = constructor.newInstance("Example", 42);
} catch (NoSuchMethodException e) {
System.err.println("找不到指定的构造方法: " + e.getMessage());
} catch (IllegalAccessException e) {
System.err.println("没有访问权限: " + e.getMessage());
} catch (InstantiationException e) {
System.err.println("实例化失败(如抽象类): " + e.getMessage());
} catch (InvocationTargetException e) {
// 最重要:获取构造方法内部抛出的原始异常
System.err.println("构造方法执行出错: " + e.getCause());
} catch (IllegalArgumentException e) {
System.err.println("参数类型或数量错误: " + e.getMessage());
}
参数匹配
- 精确匹配:调用
getDeclaredConstructor(Class<?>... parameterTypes)时,参数类型数组parameterTypes必须与目标构造方法的参数类型列表和顺序完全一致。- 示例:构造方法为
MyClass(String arg1, int arg2),则必须写为clazz.getDeclaredConstructor(String.class, int.class)。
- 示例:构造方法为
- 基本数据类型:特别注意
int.class与Integer.class是不同的。必须使用基本类型对应的.class字面量。
反射获取成员变量(Field)

获取成员变量->举例

注意事项
访问权限
getFields()与getField():只能获取public成员变量,包括从父类继承的公共字段。这可能导致获取到的字段比预期多。getDeclaredFields()与getDeclaredField():能获取本类中声明的所有成员变量(包括****private**,**protected**, 默认权限),但无法获取父类中的任何字段**。这是访问私有字段的关键。- 访问非公有字段:通过
getDeclaredField获取到非public的Field对象后,直接调用get()或set()会抛出IllegalAccessException。必须先调用field.setAccessible(true)来取消 Java 语言访问检查。 - 安全管理器 (SecurityManager):如果存在安全管理器,它可能会阻止你修改访问权限(调用
setAccessible(true)),并抛出SecurityException。
类型匹配与转换
IllegalArgumentException:调用****Field.set(Object obj, Object value)**方法时,如果传入的**value**的实际类型与字段声明的类型不兼容**,会抛出此异常。- 示例:试图将一个
String值设置到一个int类型的字段中。 - 解决方案:确保类型匹配,或进行必要的类型转换和封装(如将
int转换为Integer)。
- 示例:试图将一个
- 基本数据类型:操作基本类型(如
int,double)的字段时,需要注意自动装箱和拆箱的问题。反射 API 使用的是对象(如Integer),但字段本身是基本类型。 -
静态字段与实例字段
- 对于实例字段,
get(Object obj)和set(Object obj, Object value)中的obj参数代表要操作的那个具体对象实例。 - 对于静态字段,因为它属于类而不属于任何实例,所以
obj参数应该传入null。
// 操作静态字段
Field staticField = clazz.getDeclaredField("STATIC_FIELD");
staticField.setAccessible(true);
Object value = staticField.get(null); // 获取静态字段值,传入null
staticField.set(null, newValue); // 设置静态字段值,传入null
final 字段的特殊性
- 默认行为:即使通过
setAccessible(true)解除了访问限制,JVM 也可能阻止修改final字段的值,或者修改后行为不确定(如可能被编译器优化内联)。 - 强制修改:如果需要修改
final字段,需要更底层的操作(通过反射修改Field对象自身的modifiers属性,移除final标志),但这是一种非常规、危险的操作,会破坏语言的不可变性语义,可能导致不可预知的后果,应极度谨慎使用。
异常处理 (Exception Handling)
NoSuchFieldException:当通过名字查找字段但不存在时抛出。IllegalAccessException:当没有权限访问字段时抛出(通常是因为没调用setAccessible(true)或安全管理器阻止)。IllegalArgumentException:当传入的obj参数不是字段所属类或其子类的实例,或者值的类型不匹配时抛出。NullPointerException:操作实例字段时,如果传入的obj参数为null,则会抛出此异常。


反射获取成员方法(Method)
注意事项
权限访问
getMethods()与getMethod():只能获取public方法,包括从父类继承的方法。这可能导致获取到的方法比预期多。getDeclaredMethods()与getDeclaredMethod():能获取本类中声明的所有方法(包括****private**,**protected**, 默认权限),但无法获取父类中的任何方法**。这是访问私有方法的关键。- 访问私有方法:通过
getDeclaredMethod获取到私有Method对象后,直接调用invoke()会抛出IllegalAccessException。必须先调用method.setAccessible(true)来取消 Java 语言访问检查。 - 安全管理器 (SecurityManager):如果存在安全管理器,它可能会阻止你修改访问权限(调用
setAccessible(true)),并抛出SecurityException。
方法重载与参数匹配
精确的参数类型:调用 getMethod 和 getDeclaredMethod 时,第二个参数 Class<?>... parameterTypes 必须精确匹配目标方法的参数类型列表和顺序。这是因为 Java 存在方法重载,仅凭方法名无法唯一确定一个方法。
- 示例:如果方法定义为
add(int a, String b),那么必须写为clazz.getDeclaredMethod("add", int.class, String.class)。使用Integer.class或错误的顺序都会导致NoSuchMethodException。
调用方法时的异常处理
参数不匹配:如果调用 invoke 时传入的实参 (Object... args) 与声明的形参类型不兼容(例如,为 int 形参传入 String 实参),会抛出 IllegalArgumentException。
静态方法的调用
对对象实例参数 (**obj**):对于静态方法,不需要一个具体的对象实例。此时,invoke 方法的第一个参数** **obj** 应该传入 **null**。
Method staticMethod = clazz.getDeclaredMethod("staticMethodName");
staticMethod.invoke(null); // 第一个参数传null
练习
保存任意对象数据
先创建对象类->学生类和教师类 ,再创建反射类,通过获取成员变量 和创建IO流,将数据写入本地文件
package com.demo;
public class Student {
private String name;
private int age;
private String gender;
private double height;
private String hobby;
public Student() {
}
public Student(String name, int age, String gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}
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 String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
}
package com.demo;
public class Teacher {
private String name;
private int salary;
public Teacher() {
}
public Teacher(String name, int salary)
{
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
package com.demo;
import java.io.*;
import java.lang.reflect.Field;
public class MyReflect {
public static void main(String[] args) throws IllegalAccessException, IOException {
//创建对象
Student student = new Student("张三", 18, "男", 1.7, "football");
Teacher teacher = new Teacher("Niko",5000);
//调用方法
save( student);
}
/**
* 把对象里面所有的成员变量名和值保存到本地文件中
* @param obj
*/
public static void save(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Java\\untitled\\JavaExtendLearn\\src\\com\\demo\\text.txt"));
//2.获取所有成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);//取消访问权限检查
//3.获取成员变量的名字
String name = field.getName();
//4.获取成员变量的值
Object value = field.get( obj);
// System.out.println(name + ":" + value);
//5.把成员变量名和值保存到本地文件中
bw.write(name + ":" + value);
bw.newLine();
}
bw.close();
}
}
利用反射动态创建对象和运行方法
反射可以跟配置文件结合的方法,动态的创建方法,并调用方法
先创建prop.properties配置文件和应有的学生类和教师类
package com.demo;
public class Student {
private String name;
private int age;
//自定义方法
public void study() {
System.out.println("学生正在学习");
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//setter ,getter
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.demo;
public class Teacher {
private String name;
private int salary;
public void teacher()
{
System.out.println("老师在教书");
}
public Teacher() {
}
public Teacher(String name, int salary)
{
this.name = name;
this.salary = salary;
}
//setter getter
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
package com.demo;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* 反射工具类,用于演示Java反射机制的基本使用
* 通过读取配置文件获取类名和方法名,然后使用反射创建对象并调用指定方法
*/
public class MyReflect {
/**
* 程序入口方法
* 主要功能:
* 1. 读取配置文件获取类名和方法名
* 2. 使用反射机制创建指定类的实例
* 3. 调用指定的方法
*
* @param args 命令行参数
* @throws IOException 当文件读取失败时抛出
* @throws ClassNotFoundException 当指定的类不存在时抛出
* @throws NoSuchMethodException 当指定的方法不存在时抛出
* @throws InvocationTargetException 当方法调用过程中发生异常时抛出
* @throws InstantiationException 当类实例化失败时抛出
* @throws IllegalAccessException 当访问权限不足时抛出
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.读取配置文件中的信息
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("D:\\Java\\untitled\\JavaExtendLearn\\src\\com\\demo\\prop.properties");
prop.load(fis);
fis.close();
//2.获取配置文件中的全类名和方法名
String classname = prop.getProperty("classname");//获取配置文件中键为"classname"对应的类名值
String method = prop.getProperty("method");
System.out.println(classname);
System.out.println(method);
//3.利用反射创建对象并调用方法
Class cls = Class.forName(classname);
//获取构造方法
Constructor con = cls.getDeclaredConstructor();
Object obj = con.newInstance();
System.out.println(obj);
//获取成员方法
Method m = cls.getDeclaredMethod(method);
m.setAccessible(true);
m.invoke(obj);
}
}
创建的prop.properties
# 配置学生类的全限定名 如果想要老师类,对配置进行修改即可
# 用于指定要加载和执行的具体学生类实现
classname=com.demo.Student
# 配置要调用的方法名
# 指定学生类中需要执行的学习方法-自定义方法
method=study
更多推荐

所有评论(0)