反射的详解

发布于:2025-08-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、反射

1.JDK,JRE,JVM的关系

三者是Java运行环境的核心组成部分,从包含关系上看:JDK 包含JRE包含JVM,具体作用如下:

  • JDK(Java Development Kit,Java开发工具包)
    是Java开发的工具集合,包含 JRE + 编译器(javac)、调试工具(jdb)、文档工具(javadoc)等开发必需的工具。如果需要编写、编译Java程序,必须安装JDK。

  • JRE(Java Runtime Environment,Java运行时环境)
    运行Java程序的最小环境,包含 JVM + 运行Java程序必需的类库(如java.lang包等)。如果只需要运行Java程序(如 .class 或 .jar 文件),安装JRE即可。

  • JVM(Java Virtual Machine,Java虚拟机)
    是运行Java字节码的虚拟计算机,负责将字节码翻译成具体操作系统可执行的机器码。它是Java“一次编写,到处运行”(跨平台)的核心,不同操作系统需要安装对应的JVM实现。

  • 字节码的运行过程:

    编译阶段:开发者编写的Java源代码( .java 文件),通过JDK中的编译器(javac)编译成字节码文件( .class 文件),字节码是一种与平台无关的二进制指令。

    类加载:运行时,JVM的类加载器(ClassLoader)将 .class 文件(磁盘或者其他地方)加载到JVM内存中。

    字节码执行:JVM中的执行引擎(如解释器或即时编译器JIT)将字节码翻译成当前操作系统可识别的机器码,最终由操作系统执行机器码,完成程序功能。

    简单来说,字节码是Java跨平台的“中间语言”,通过JVM在不同系统上的适配,实现了“一次编译,到处运行”。

2.什么是反射

​ 反射是允许程序在运行期间可以获取类的类型( Class 对象(字节码对象)),成员变量,方法并可以动态的创建对象,访问属性,调用方法之类的操作。例如在往spring容器中注入一个bean对象,就是通过指定bean的class类型,然后spring框架在启动的时候就可以创建出这个bean对象。

具体:

1.“获取类的类型” 的本质:
这里的 “类型” 其实就是 Class 对象(字节码对象),它是反射的入口。比如 Spring 中指定 class="com.example.User",框架会通过 Class.forName("com.example.User") 获取 Class 对象,进而操作这个类。
2.动态性的核心体现:
你提到的 “动态创建对象、访问属性、调用方法”,核心是不需要在编译期写死具体类名或方法名。例如 Spring 配置文件中改一下 class 属性的值,无需重新编译代码,就能创建不同的对象,这就是反射动态性的价值。
3.框架中的典型流程:
以 Spring 注入 Bean 为例,反射的具体步骤是:
读取配置文件中的 class 属性(全类名);
通过 Class.forName() 获取 Class 对象;
通过 Constructor.newInstance() 动态创建实例(反射创建对象);
若有属性注入,再通过 Field.set() 动态设置属性值(反射操作属性)。

3. 三种获取Class对象(类的字节码)的方式

方法一:直接通过一个 class 的静态变量 class 获取:

Class cls = String.class;

方法二:如果我们有一个实例变量,可以通过该实例变量提供的 getClass () 方法获取:

String s = "Hello";
Class cls = s.getClass();

方法三:如果知道一个 class 的完整类名,可以通过静态方法 Class.forName () 获取:

Class cls = Class.forName("java.lang.String");

注意:因为 Class 实例在 JVM 中是唯一的,所以,上述方法获取的 class 实例是同一个实例。可以用 == 或hashcode()比较实例。

eg:

public class Test2 {

    public static void main(String[] args) throws ClassNotFoundException {
        Class  class1 = Class.forName("第二周.day3.Order");
        Class<Order> class2 = Order.class;
        Class class3 = new Order().getClass();
        System.out.println(class1.hashCode());//2003749087
        System.out.println(class2.hashCode());//2003749087
        System.out.println(class3.hashCode());//2003749087
    }
}
class  Order{
    static {
        System.out.println("静态代码块中Order已经执行!!!");
    }
}

4.Class常用方法

获取父类的Class,获取实现接口的Class:

public class Test3 {
    public static void main(String[] args) {
        Class<String> stringClass = String.class;
        System.out.println("完全限定类名:" + stringClass.getName());
        System.out.println("完全限定类名:" + stringClass.getTypeName());
        System.out.println("仅包含类名:" + stringClass.getSimpleName());
        System.out.println("所在的包名:" + stringClass.getPackage());
        // 获取父类的 Class 对象
        //String 类继承自 java.lang.Object,所以返回 Object 类的 Class 实例
        Class<? super String> superclass = stringClass.getSuperclass();
        System.out.println("父类:" + superclass);
        //  获取类实现的接口(String 类实现了多个接口,如 Serializable、Comparable等)
        Class[] interfaces = stringClass.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println("接口:" + anInterface);
            
        }
    }
}

判断继承关系:

public class Test7 {
    public static void main(String[] args) {
        // instanceof 关键字:判断对象是否是某个类型的实例(被注释示例)
        // Integer n = 2;
        // System.out.println(n instanceof Integer); // true(自身类型)
        // System.out.println(n instanceof Number);  // true(父类)


        // isAssignableFrom:判断类型B能否赋值给类型A(Class类方法)
        
        // 1. Integer能否赋值给Integer(自身类型)
        System.out.println(Integer.class.isAssignableFrom(Integer.class)); // true
        
        // 2. Number能否赋值给Integer(父类→子类,不成立)
        System.out.println(Integer.class.isAssignableFrom(Number.class));  // false
        
        // 3. Integer能否赋值给Number(子类→父类,成立)
        System.out.println(Number.class.isAssignableFrom(Integer.class));  // true
    }
}

判断Class的类型:

public class Test3 {
    public static void main(String[] args) {
        // 获取不同类型的Class对象
        Class clz1 = String.class;        // 普通类
        Class clz2 = DayOfWeek.class;     // 枚举类
        Class clz3 = String[].class;      // 数组类型
        Class clz4 = List.class;          // 接口
        
        // 调用info方法打印类信息
        info(clz1);
        info(clz2);
        info(clz3);
        info(clz4);
    }
    
    // 打印类的基本类型信息
    public static void info(Class clazz) {
        System.out.println("类信息:" + clazz);
        System.out.println("是否是接口? " + clazz.isInterface());
        System.out.println("是否是枚举? " + clazz.isEnum());
        System.out.println("是否是数组? " + clazz.isArray());
        System.out.println("---------------------"); // 分隔线
    }
}

5. 获取类的构造器

过字节码对象获取构造器,并使用构造器创建对象。

在这里插入图片描述

get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Constructor: 构造方法的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个

反射获取构造器的作用:初始化对象并返回

在这里插入图片描述

eg:

package 第二周.day3;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Test5 {
    public static void main(String[] args)
            throws NoSuchMethodException,  // 当找不到指定构造方法时抛出
            InvocationTargetException,  // 构造方法执行过程中抛出异常时包装此异常
            InstantiationException,  // 当类是抽象类或接口等无法实例化时抛出
            IllegalAccessException  // 构造方法访问权限不足时抛出
    {
        // 获取Animal类的Class对象
        // Class对象是反射的核心,包含了类的所有元信息(构造方法、方法、字段等)
        Class clazz = Animal.class;

        // 获取指定参数列表的构造方法
        // getConstructor()方法参数为构造方法的参数类型对应的Class对象
        Constructor constructor0 = clazz.getConstructor(); // 获取无参构造方法
        Constructor constructor1 = clazz.getConstructor(int.class); // 获取接收int类型参数的构造方法
        Constructor constructor2 = clazz.getConstructor(int.class, double.class); // 获取接收int和double类型参数的构造方法

        // 通过反射获取的Constructor对象创建Animal实例
        // newInstance()方法的参数为实际传递给构造方法的参数值
        System.out.println("===========");
        Object o1 = constructor0.newInstance();  // 调用无参构造方法创建实例
        // 打印创建的对象(默认调用Object类的toString()方法,输出类名@哈希码)
        System.out.println(o1);

        System.out.println("===========");
        Object o2 = constructor1.newInstance(23);  // 调用int参数的构造方法,传入23
        System.out.println(o2);

        System.out.println("===========");
        Object o3 = constructor2.newInstance(23, 3.14);  // 调用int和double参数的构造方法,传入23和3.14
        System.out.println(o3);

    }
}

class Animal {
    // 静态代码块:在类加载阶段执行,且只执行一次
    // 用于验证类加载的时机(当获取Class对象时会触发类加载)
    static {
        System.out.println("Animal类被加载!!!!");
    }

    // 无参构造方法
    public Animal() {
        System.out.println("无参构造方法被执行。");
    }

    // 接收int类型参数的构造方法
    public Animal(int a) {
        System.out.println("有参构造方法被执行:a = " + a);
    }

    // 接收int和double类型参数的构造方法
    public Animal(int a, double b) {
        System.out.println("有参构造方法被执行:a = " + a + ",b = " + b);
    }
}

6.反射获取成员变量&使用

在Class类中提供了获取成员变量的方法:

在这里插入图片描述

再次强调一下设置值、获取值的方法时Filed类的需要用Filed类的对象来调用,而且不管是设置值、还是获取值,都需要依赖于该变量所属的对象。代码如下:

设置值:

import java.lang.reflect.Field;

// 演示 Java 反射机制:通过 Class 对象操作 Employee 类的私有成员
public class Test8 {
    public static void main(String[] args) throws Exception {
        // ========== 1. 获取 Class 对象(反射入口) ==========
        // 方式:类名.class,获取 Employee 类的字节码元数据
        Class clazz = Employee.class;


        // ========== 2. 反射创建对象 ==========
        // 调用无参构造器实例化对象(要求 Employee 有无参构造)
        Object obj = clazz.newInstance();


        // ========== 3. 反射获取私有字段 ==========
        // getDeclaredField 可获取私有字段,需配合 setAccessible(true) 突破封装
        Field enoField = clazz.getDeclaredField("eno");
        Field nameField = clazz.getDeclaredField("realName");
        Field salaryField = clazz.getDeclaredField("salary");


        // ========== 4. 暴力反射:开启私有字段访问权限 ==========
        // 即使字段是 private,也能通过此方法赋值
        enoField.setAccessible(true);
        nameField.setAccessible(true);
        salaryField.setAccessible(true);


        // ========== 5. 反射赋值私有字段 ==========
        // 等价于:obj.eno = "T001"; 
        enoField.set(obj, "T001");
        // 等价于:obj.realName = "水产张总";
        nameField.set(obj, "水产张总");
        // 等价于:obj.salary = 456.778;
        salaryField.setDouble(obj, 456.778);


        // ========== 6. 验证结果 ==========
        // 调用 Employee 的 toString() 输出对象内容
        System.out.println(obj);
    }
}

// 普通 Java 类,包含私有字段和 toString 方法
class Employee {
    // 私有字段:体现封装性
    private String eno;
    private String realName;
    private String phoneNumber;
    private int level;
    private double salary;

    // 重写 toString,自定义对象打印格式
    @Override
    public String toString() {
        return "Employee{" +
                "eno='" + eno + '\'' +
                ", realName='" + realName + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", level=" + level +
                ", salary=" + salary +
                '}';
    }
}

取值:

public class Test9 {
    public static void main(String[] args) throws IllegalAccessException {
        // 1. 创建 Employee 对象(通过构造器传参初始化)
        Employee emp = new Employee("T002", "孙乐", "1310988442", 8, 4567.8);
        // 2. 调用 info 方法,反射打印对象字段信息
        info(emp);
    }

    public static void info(Object obj) throws IllegalAccessException {
        // 3. 获取对象的 Class 对象(反射入口)
        Class clazz = obj.getClass();
        
        // 4. 获取类的所有**声明的字段**(包括 private)
        Field[] fields = clazz.getDeclaredFields();
        
        // 5. 遍历字段,逐个处理
        for (Field f : fields) {
            // 6. 暴力反射:突破 private 限制(允许访问私有字段)
            f.setAccessible(true);
            
            // 7. 获取字段的类型(判断是 int、double 还是其他类型)
            Class fieldType = f.getType();
            
            // 8. 根据字段类型,调用不同的 get 方法
            if (fieldType == int.class) {
                // int 类型:用 getInt 获取值
                System.out.printf("%s = %s%n", f.getName(), f.getInt(obj));
            } else if (fieldType == double.class) {
                // double 类型:用 getDouble 获取值
                System.out.printf("%s = %s%n", f.getName(), f.getDouble(obj));
            } else {
                // 其他类型(如 String):用 get 获取值
                System.out.printf("%s = %s%n", f.getName(), f.get(obj));
            }
        }
    }
}

// 员工类:包含私有字段和构造器
class Employee {
    // 私有字段(体现封装性)
    private String eno;
    private String realName;
    private String phoneNumber;
    private int level;
    private double salary;

    // 无参构造(未使用,但保留)
    public Employee() {}

    // 全参构造:用于初始化对象
    public Employee(String eno, String realName, String phoneNumber, int level, double salary) {
        this.eno = eno;
        this.realName = realName;
        this.phoneNumber = phoneNumber;
        this.level = level;
        this.salary = salary;
    }

    // 重写 toString:自定义对象打印格式(main 中未直接调用,但可用于调试)
    @Override
    public String toString() {
        return "Employee{" +
                "eno='" + eno + '\'' +
                ", realName='" + realName + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", level=" + level +
                ", salary=" + salary +
                '}';
    }
}

7.反射获取成员方法

在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。

在这里插入图片描述

eg:

import java.lang.reflect.Method;

// Class 类型:用来封装某一个class类的类型信息(类名、构造方法、成员变量[字段]、实例方法)
// Constructor类型:用来封装一个构造方法
// Field类型:用来封装一个成员变量[字段]
// Method类型:用于封装一个方法
public class Test10 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 硬编码
        // String s = new String("just do IT");
        // String result = s.substring(0,4);
        // System.out.println(result);

         //1、反射第一步:先获取到Class对象
        Class clazz = String.class;

        // 创建实例对象
        Constructor constructor = clazz.getConstructor(String.class);
        Object s = constructor.newInstance("just do IT");

        // 根据方法名称,获取对应的Method对象
        Method method = clazz.getDeclaredMethod("substring", int.class, int.class);

        // invoke()执行方法
        // 硬编码 String result = s.substring(0,4);
        Object returnValue = method.invoke(s, 0, 4);
        System.out.println("返回值:" + returnValue);

        // 获取String类的所有定义方法
        // Method[] methods = clazz.getDeclaredMethods();
        // for (Method method : methods){
        //     System.out.println(method);
        // }
    }
}

eg:静态方法的调用,传对象为null

import java.lang.reflect.Method;

// 静态方法的调用
public class Test11 {
    public static void main(String[] args) 
        throws NoSuchMethodException, 
               InvocationTargetException, 
               IllegalAccessException {
        
        // ========== 硬编码方式(被注释示例) ==========
        // 直接调用 Math.log10(567),并输出结果+1
        // int result = (int) Math.log10(567);
        // System.out.println(result + 1);


        // ========== 反射方式调用静态方法 ==========
        // 1. 获取 Math 类的 Class 对象
        Class clazz = Math.class;

        // 2. 获取静态方法 log10:参数类型是 double
        Method method = clazz.getDeclaredMethod("log10", double.class);

        // 3. 调用静态方法:因静态方法属于类,而非对象,所以 invoke 的第一个参数传 null
        Object returnValue = method.invoke(null, 567.0);

        // 4. 输出反射调用结果
        System.out.println(returnValue);
    }
}

8.综合例子

package 第二周.day3;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Test13 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            while (true) {
                System.out.print("请输入目标工具类名:");
                String className = input.nextLine(); // 第二周.day3.Command

                System.out.print("请输入目标方法名称:");
                String methodName = input.nextLine(); // grow
                if ("exit".equals(className) && "null".equals(methodName)) {
                    System.out.println("程序退出了");
                    break;
                }
                // 判断方法是否有参数,并执行该方法
                Class classzz = Class.forName(className);

                Method[] declaredMethods = classzz.getDeclaredMethods();
                Method targetmethod = null;
                for (Method method : declaredMethods) {
                    if (methodName.equals(method.getName())) {
                        targetmethod = method;
                        break;
                    }
                }
                if (targetmethod == null) {
                    System.out.println("目标方法" + methodName + "不存在");
                    continue;
                }

                Object[] objs = new Object[targetmethod.getParameterCount()];
                //遍历的当前方法有几个参数,就设置几次容量,传几次参数
                for (int i = 1; i <= targetmethod.getParameterCount(); i++) {
                    System.out.println("请输入第:" + i + "个参数");
                    objs[i - 1] = input.nextInt();
                }

                //创建实例
                Constructor constructor = classzz.getConstructor();
                Object o = constructor.newInstance();
                targetmethod.invoke(o, objs);


            }
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }
}

class Command {
    public Command() {
    }

    public void clear() {
        System.out.println("执行清空操作!");
    }

    public void init(int initSize, int initLocation) {
        System.out.println("执行初始化操作!");
        System.out.println("初始化长度 = " + initSize);
        System.out.println("初始化位置 = " + initLocation);
    }

    public void grow(int size) {
        System.out.println("执行扩容操作!");
        System.out.println("扩容量 = " + size);
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到