概述

反射就是把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 获取到非 publicConstructor 对象后,直接调用 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.classInteger.class 是不同的。必须使用基本类型对应的 .class 字面量。

反射获取成员变量(Field)

获取成员变量->举例

注意事项

访问权限
  • getFields()getField():只能获取 public 成员变量,包括从父类继承的公共字段。这可能导致获取到的字段比预期多。
  • getDeclaredFields()getDeclaredField():能获取本类中声明的所有成员变量(包括** **private**, **protected**, 默认权限),但无法获取父类中的任何字段**。这是访问私有字段的关键。
  • 访问非公有字段:通过 getDeclaredField 获取到非 publicField 对象后,直接调用 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
方法重载与参数匹配

精确的参数类型:调用 getMethodgetDeclaredMethod 时,第二个参数 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
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐