第40章 Java反射(Reflection)

运行时类信息获取与动态操作详解

什么是Java反射

Java反射(Reflection)是Java语言的一个重要特性,它允许程序在运行时检查和操作类、接口、字段和方法的信息。通过反射,我们可以在运行时动态地创建对象、调用方法、访问和修改字段,甚至可以获取类的结构信息。

反射的核心概念

反射机制允许程序在运行期间获取任意一个类的内部信息,并能够操作任意一个对象的内部属性及方法。这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

反射的主要用途

运行时类型检查

在运行时确定对象的类型,获取类的详细信息,包括类名、包名、父类、接口等。

动态对象创建

根据类名动态创建对象实例,支持不同参数的构造器调用。

动态方法调用

在运行时调用对象的方法,包括私有方法,实现灵活的程序控制。

动态字段访问

获取和修改对象的字段值,包括私有字段,突破访问限制。

反射核心类

Java反射机制主要通过以下几个核心类来实现:

Class类

代表类的实体,在运行的Java应用程序中表示类和接口。每个类都有一个Class对象。

Field类

代表类的成员变量(字段),提供有关类或接口的单个字段的信息和动态访问权限。

Method类

代表类的方法,提供关于类或接口上单独某个方法的信息和访问权限。

Constructor类

代表类的构造器,提供关于类的单个构造器的信息以及对它的访问权限。

获取Class对象

获取Class对象是使用反射的第一步,Java提供了三种方式来获取Class对象:

ReflectionBasics.java - Class对象获取示例
import java.lang.reflect.*;

public class ReflectionBasics {
    
    public static void demonstrateClassObject() {
        System.out.println("=== 获取Class对象的三种方式 ===");
        
        try {
            // 方式1:类名.class
            Class clazz1 = String.class;
            System.out.println("方式1 - 类名.class: " + clazz1.getName());
            
            // 方式2:对象.getClass()
            String str = "Hello";
            Class clazz2 = str.getClass();
            System.out.println("方式2 - 对象.getClass(): " + clazz2.getName());
            
            // 方式3:Class.forName()
            Class clazz3 = Class.forName("java.lang.String");
            System.out.println("方式3 - Class.forName(): " + clazz3.getName());
            
            // 验证三种方式获取的是同一个Class对象
            System.out.println("三种方式获取的Class对象相等: " + (clazz1 == clazz2 && clazz2 == clazz3));
            
        } catch (ClassNotFoundException e) {
            System.err.println("类未找到: " + e.getMessage());
        }
    }
    
    public static void demonstrateClassInfo() {
        System.out.println("\n=== 获取类的基本信息 ===");
        
        Class clazz = String.class;
        
        System.out.println("类名: " + clazz.getName());
        System.out.println("简单类名: " + clazz.getSimpleName());
        System.out.println("规范类名: " + clazz.getCanonicalName());
        
        Package pkg = clazz.getPackage();
        if (pkg != null) {
            System.out.println("包名: " + pkg.getName());
        }
        
        int modifiers = clazz.getModifiers();
        System.out.println("修饰符: " + Modifier.toString(modifiers));
        System.out.println("是否为public: " + Modifier.isPublic(modifiers));
        System.out.println("是否为final: " + Modifier.isFinal(modifiers));
        
        Class superClass = clazz.getSuperclass();
        if (superClass != null) {
            System.out.println("父类: " + superClass.getName());
        }
        
        Class[] interfaces = clazz.getInterfaces();
        System.out.println("实现的接口数量: " + interfaces.length);
        for (Class intf : interfaces) {
            System.out.println("  - " + intf.getName());
        }
    }
    
    public static void main(String[] args) {
        demonstrateClassObject();
        demonstrateClassInfo();
    }
}

获取Class对象的方式对比

  • 类名.class:编译时就确定,效率最高,适用于已知具体类的情况
  • 对象.getClass():运行时获取,适用于已有对象实例的情况
  • Class.forName():动态加载类,适用于类名以字符串形式提供的情况

字段反射操作

通过反射可以获取类的字段信息,并对字段进行读取和修改操作,甚至可以访问私有字段:

FieldReflection.java - 字段反射示例
import java.lang.reflect.*;

public class FieldReflection {
    
    public String publicField = "公共字段";
    private String privateField = "私有字段";
    protected int protectedField = 100;
    static final String CONSTANT = "常量字段";
    
    public static void demonstrateGetFields() {
        System.out.println("=== 获取类的字段信息 ===");
        
        Class clazz = FieldReflection.class;
        
        // 获取所有公共字段(包括继承的)
        System.out.println("\n公共字段(getFields):");
        Field[] publicFields = clazz.getFields();
        for (Field field : publicFields) {
            System.out.println("  " + field.getName() + " - " + field.getType().getSimpleName());
        }
        
        // 获取所有声明的字段(不包括继承的)
        System.out.println("\n所有声明的字段(getDeclaredFields):");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            String modifiers = Modifier.toString(field.getModifiers());
            System.out.println("  " + modifiers + " " + field.getType().getSimpleName() + " " + field.getName());
        }
    }
    
    public static void demonstrateFieldAccess() {
        System.out.println("\n=== 字段访问和修改 ===");
        
        try {
            FieldReflection obj = new FieldReflection();
            Class clazz = obj.getClass();
            
            // 访问公共字段
            Field publicField = clazz.getField("publicField");
            System.out.println("公共字段原值: " + publicField.get(obj));
            publicField.set(obj, "修改后的公共字段");
            System.out.println("公共字段新值: " + publicField.get(obj));
            
            // 访问私有字段
            Field privateField = clazz.getDeclaredField("privateField");
            privateField.setAccessible(true); // 设置可访问
            System.out.println("私有字段原值: " + privateField.get(obj));
            privateField.set(obj, "修改后的私有字段");
            System.out.println("私有字段新值: " + privateField.get(obj));
            
            // 访问静态字段
            Field constantField = clazz.getDeclaredField("CONSTANT");
            System.out.println("静态常量字段: " + constantField.get(null));
            
        } catch (NoSuchFieldException | IllegalAccessException e) {
            System.err.println("字段访问错误: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        demonstrateGetFields();
        demonstrateFieldAccess();
    }
}

方法反射操作

方法反射允许我们在运行时获取类的方法信息并动态调用方法,包括私有方法和重载方法:

MethodReflection.java - 方法反射示例
import java.lang.reflect.*;

public class MethodReflection {
    
    public String publicMethod(String param) {
        return "公共方法被调用,参数: " + param;
    }
    
    private int privateMethod(int a, int b) {
        return a + b;
    }
    
    public static void staticMethod() {
        System.out.println("静态方法被调用");
    }
    
    // 重载方法示例
    public void overloadedMethod() {
        System.out.println("无参数的重载方法");
    }
    
    public void overloadedMethod(String param) {
        System.out.println("带String参数的重载方法: " + param);
    }
    
    public void overloadedMethod(int param) {
        System.out.println("带int参数的重载方法: " + param);
    }
    
    public static void demonstrateMethodInvocation() {
        System.out.println("=== 方法调用 ===");
        
        try {
            MethodReflection obj = new MethodReflection();
            Class clazz = obj.getClass();
            
            // 调用公共方法
            Method publicMethod = clazz.getMethod("publicMethod", String.class);
            Object result1 = publicMethod.invoke(obj, "测试参数");
            System.out.println("公共方法调用结果: " + result1);
            
            // 调用私有方法
            Method privateMethod = clazz.getDeclaredMethod("privateMethod", int.class, int.class);
            privateMethod.setAccessible(true);
            Object result2 = privateMethod.invoke(obj, 10, 20);
            System.out.println("私有方法调用结果: " + result2);
            
            // 调用静态方法
            Method staticMethod = clazz.getMethod("staticMethod");
            staticMethod.invoke(null); // 静态方法传入null
            
            // 调用重载方法
            Method overloaded1 = clazz.getMethod("overloadedMethod");
            overloaded1.invoke(obj);
            
            Method overloaded2 = clazz.getMethod("overloadedMethod", String.class);
            overloaded2.invoke(obj, "字符串参数");
            
            Method overloaded3 = clazz.getMethod("overloadedMethod", int.class);
            overloaded3.invoke(obj, 42);
            
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            System.err.println("方法调用错误: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        demonstrateMethodInvocation();
    }
}

构造器反射操作

构造器反射允许我们在运行时动态创建对象实例,支持不同参数的构造器:

ConstructorReflection.java - 构造器反射示例
import java.lang.reflect.*;

public class ConstructorReflection {
    
    private String name;
    private int age;
    private String email;
    
    // 多个构造器示例
    public ConstructorReflection() {
        this.name = "默认姓名";
        this.age = 0;
        this.email = "default@example.com";
        System.out.println("无参构造器被调用");
    }
    
    public ConstructorReflection(String name) {
        this.name = name;
        this.age = 0;
        this.email = "default@example.com";
        System.out.println("单参数构造器被调用: " + name);
    }
    
    public ConstructorReflection(String name, int age) {
        this.name = name;
        this.age = age;
        this.email = "default@example.com";
        System.out.println("双参数构造器被调用: " + name + ", " + age);
    }
    
    public ConstructorReflection(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
        System.out.println("全参数构造器被调用: " + name + ", " + age + ", " + email);
    }
    
    private ConstructorReflection(int age) {
        this.name = "私有构造";
        this.age = age;
        this.email = "private@example.com";
        System.out.println("私有构造器被调用: " + age);
    }
    
    public String getInfo() {
        return String.format("姓名: %s, 年龄: %d, 邮箱: %s", name, age, email);
    }
    
    public static void demonstrateObjectCreation() {
        System.out.println("=== 通过反射创建对象 ===");
        
        try {
            Class clazz = ConstructorReflection.class;
            
            // 使用无参构造器
            Constructor noArgConstructor = clazz.getConstructor();
            Object obj1 = noArgConstructor.newInstance();
            System.out.println("无参构造对象: " + ((ConstructorReflection) obj1).getInfo());
            
            // 使用单参数构造器
            Constructor oneArgConstructor = clazz.getConstructor(String.class);
            Object obj2 = oneArgConstructor.newInstance("张三");
            System.out.println("单参数构造对象: " + ((ConstructorReflection) obj2).getInfo());
            
            // 使用双参数构造器
            Constructor twoArgConstructor = clazz.getConstructor(String.class, int.class);
            Object obj3 = twoArgConstructor.newInstance("李四", 25);
            System.out.println("双参数构造对象: " + ((ConstructorReflection) obj3).getInfo());
            
            // 使用全参数构造器
            Constructor fullArgConstructor = clazz.getConstructor(String.class, int.class, String.class);
            Object obj4 = fullArgConstructor.newInstance("王五", 30, "wangwu@example.com");
            System.out.println("全参数构造对象: " + ((ConstructorReflection) obj4).getInfo());
            
            // 使用私有构造器
            Constructor privateConstructor = clazz.getDeclaredConstructor(int.class);
            privateConstructor.setAccessible(true);
            Object obj5 = privateConstructor.newInstance(35);
            System.out.println("私有构造对象: " + ((ConstructorReflection) obj5).getInfo());
            
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            System.err.println("对象创建错误: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        demonstrateObjectCreation();
    }
}
💻 查看完整代码 - 在线IDE体验

反射最佳实践

使用建议

  • 谨慎使用反射,优先考虑常规的面向对象编程方式
  • 反射操作要进行异常处理,捕获相关的反射异常
  • 使用setAccessible(true)时要考虑安全性问题
  • 反射操作性能较低,避免在性能敏感的代码中频繁使用
  • 缓存反射获取的Class、Method、Field对象以提高性能
  • 在框架开发中合理使用反射,提供灵活性和扩展性

注意事项

  • 性能影响:反射操作比直接调用慢,不适合性能敏感场景
  • 安全限制:可能受到安全管理器的限制
  • 代码维护:反射代码难以理解和维护,降低代码可读性
  • 编译时检查:反射操作无法在编译时进行类型检查

反射应用场景

框架开发

Spring、Hibernate等框架大量使用反射实现依赖注入、ORM映射等功能。

序列化

JSON、XML序列化库使用反射将对象转换为字符串格式。

调试工具

IDE调试器使用反射显示对象的内部状态和结构信息。

插件系统

动态加载和执行插件代码,实现可扩展的应用架构。