Java基础【反射】

什么是反射

反射是指在程序运行时检查、获取和操作类的信息的能力。在 Java

中,反射机制允许程序在运行时动态地加载类、调用方法、访问属性等,而不需要在编译时确定这些元素。反射机制提供了一种途径,使得程序可以在运行时获取类的信息并对其进行操作,这使得程序具有更大的灵活性和可扩展性。

使用反射机制,可以做到以下几点:

  1. 获取类的基本信息:包括类的名称、方法、字段等。
  2. 创建类的实例:可以根据类的名称动态地创建对象。
  3. 调用类的方法:可以动态地调用类的方法。
  4. 访问类的属性:可以动态地访问和修改类的字段值。

反射的原理

  1. 获取Class对象: 反射的第一步是获取需要操作的类的Class对象。可以通过类的全限定名、对象实例的getClass()方法或Class类的静态forName()方法来获取。

  2. 操作类的信息: 通过Class对象可以获取类的信息,包括类的名称、父类、接口、方法、字段等。

  3. 创建对象: 反射允许在运行时动态地创建类的对象。可以通过Class对象的newInstance()方法来实例化一个类的对象。

  4. 调用方法: 反射可以调用类的方法,包括公共方法、私有方法以及静态方法。通过Method类的invoke()方法可以调用方法,并传入相应的参数。

  5. 访问和修改字段: 反射允许访问和修改类的字段(成员变量),包括公共字段、私有字段以及静态字段。通过Field类的get()和set()方法可以读取和修改字段的值。

  6. 动态代理: 反射还可以实现动态代理,通过Proxy类和InvocationHandler接口可以动态地创建代理对象,实现对目标对象的调用拦截和增强。

如何使用反射

获取 Class 对象

java 复制代码
Class<?> clazz = MyClass.class; // MyClass是你要反射的类

创建对象

java 复制代码
Class<?> clazz = MyClass.class;
MyClass obj = (MyClass) clazz.newInstance(); // MyClass是你要创建对象的类

调用方法

java 复制代码
Class<?> clazz = MyClass.class;
Object obj = clazz.newInstance();
Method method = clazz.getMethod("methodName", parameterTypes); // methodName是方法名,parameterTypes是方法参数类型数组
Object result = method.invoke(obj, args); // args是方法参数数组

访问字段

java 复制代码
Class<?> clazz = MyClass.class;
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("fieldName"); // fieldName是字段名
field.setAccessible(true); // 设置可访问私有字段
Object value = field.get(obj);
field.set(obj, newValue);

获取构造方法

java 复制代码
Class<?> clazz = MyClass.class;
Constructor<?> constructor = clazz.getConstructor(parameterTypes); // parameterTypes是构造方法参数类型数组
MyClass obj = (MyClass) constructor.newInstance(args); // args是构造方法参数数组

注意事项:

反射操作可能会影响性能,应谨慎使用,尽量避免频繁调用。

反射操作可能会导致编译时检查失效,需确保操作正确,否则可能会引发运行时异常。

反射操作可能会降低代码的可读性和维护性,应谨慎使用。

应用实例

假设有一个简单的类 Person 如下所示:

java 复制代码
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void displayInfo() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}

使用反射创建对象并调用方法

java 复制代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取Person类的Class对象
        Class<?> personClass = Class.forName("Person");

        // 创建Person类的对象
        Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
        Object person = constructor.newInstance("Alice", 25);

        // 访问和修改字段
        Field nameField = personClass.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(person, "Bob");

        // 调用方法
        Method displayInfoMethod = personClass.getDeclaredMethod("displayInfo");
        displayInfoMethod.invoke(person);
    }
}

通常情况下,我们在编写代码时需要知道要使用的类的名称、方法名、字段名等,然后才能调用相应的方法或访问字段。但是,使用反射机制,我们可以在运行时通过获取类的Class对象,动态地获取类的信息,并且可以在不知道类的具体信息的情况下创建对象、调用方法、访问和修改字段等。

假设我们有一个通用的方法,可以根据类的名称和方法名来调用方法:

java 复制代码
public void invokeMethod(String className, String methodName) throws Exception {
    Class<?> clazz = Class.forName(className);
    Object obj = clazz.newInstance();
    Method method = clazz.getMethod(methodName);
    method.invoke(obj);
}

具体会用到的场景
框架和库的设计:许多框架和库利用反射机制实现插件式架构,允许用户编写自定义代码并在运行时动态加载和执行。

配置文件解析:通过读取配置文件中的类名、方法名等信息,使用反射来实例化对象、调用方法,从而实现灵活的配置管理。

ORM 框架:对象关系映射(ORM)框架可以利用反射来将数据库记录映射到 Java 对象,从而实现数据的持久化和对象的操作。

单元测试:在单元测试中,可以使用反射来访问私有方法、字段,以及调用特定的构造方法,帮助进行测试覆盖率和边界情况的验证。

动态代理:使用反射可以实现动态代理,例如在 AOP(面向切面编程)中实现切面功能,对方法的调用进行增强处理。

依赖注入:通过反射机制,可以实现依赖注入框架,自动装配对象并解决对象之间的依赖关系。

反射调试工具:开发调试工具可以利用反射机制获取类的信息、调用方法,帮助分析代码结构和执行过程。

以下是一个简单的示例,演示了如何使用反射和动态代理来实现日志记录的切面功能

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 切面类
public class LoggingAspect implements InvocationHandler {
    private Object target;

    public LoggingAspect(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法执行前记录日志
        System.out.println("Before method: " + method.getName());
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        // 在方法执行后记录日志
        System.out.println("After method: " + method.getName());
        
        return result;
    }

    // 创建代理对象
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LoggingAspect(target)
        );
    }
}

// 目标类
public interface Calculator {
    int add(int a, int b);
}

// 目标类的实现
public class BasicCalculator implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

// 主程序
public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        Calculator calculator = new BasicCalculator();
        
        // 创建切面代理对象
        Calculator proxyCalculator = LoggingAspect.createProxy(calculator);
        
        // 调用代理对象的方法
        int result = proxyCalculator.add(1, 2); // 此时会输出日志信息
    }
}
java 复制代码
Calculator proxyCalculator = LoggingAspect.createProxy(calculator);

invock()方法的隐式调用逻辑

  1. 创建隐式代理对象
  2. 创建隐式代理对象时会根据我们传入的对象,反射出proxy类的子类proxy0,并通过这个子类反射得出父类proxy的构造器,将我们自己写的 InvocationHander实现类 LoggingAspect传入。则其父类proxy调用的invoke方法是我们自己的方法。
  3. 最后实现了invoke的隐式调用
相关推荐
2401_857439692 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna2 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_2 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹3 小时前
基于java的改良版超级玛丽小游戏
java
梧桐树04293 小时前
python常用内建模块:collections
python
Dream_Snowar3 小时前
速通Python 第三节
开发语言·python
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭4 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫4 小时前
泛型(2)
java
超爱吃士力架4 小时前
邀请逻辑
java·linux·后端
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论