一篇文章搞懂 java 反射

300 阅读4分钟

一篇文章搞懂 java 反射

简述

Java 反射机制是指在程序运行状态中,对于任意一个类,都能获取其所有属性和方法;对于任意一个对象,都能调用其任意方法和属性。这种动态获取信息与动态调用对象方法的能力,就是 Java 反射机制的核心。

反射基础

  • RTTI(运行时类型识别) :区别于编译时已确定的类型,RTTI 能在运行时识别对象或类的信息,而反射是 RTTI 的重要实现方式。
  • 反射的本质:将类的属性、方法、构造器等组件 “解剖” 为一个个可操作的对象,从而实现动态操作类的能力。

Class 类

Java 中每个类或接口在 JVM 中都对应一个Class对象(包括基本数据类型和 void),它是反射的核心入口。

Class 类的特点

  • java.lang包下的 final 类,实现了多个接口。

  • 构造方法为private,仅由 JVM 创建和加载,开发者无法手动实例化。

  • 每个类在 JVM 中仅有一个对应的Class对象,全局唯一。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    // 源码片段:构造方法私有
    private Class(ClassLoader loader) {
        classLoader = loader;
    }
}

类加载机制

  1. 编译:Java 文件编译为.class文件,包含类的完整信息。

  2. 加载:类加载器(ClassLoader)将.class文件加载到内存。

  3. 生成 Class 对象:JVM 为每个类创建唯一的Class对象,后续所有实例对象都基于该Class对象创建。

示意图逻辑
本地 / 网络的.class文件 → 类加载器 → 内存生成唯一Class对象 → 基于Class对象创建多个实例对象。

反射的核心类库

Java 反射主要依赖java.lang.Class类和java.lang.reflect包,其中关键类包括:

  • Constructor:类的构造方法
  • Field:类的成员变量
  • Method:类的成员方法
  • Modifier:访问修饰符工具类

常用方法速查表

方法说明
forName(String)通过全限定类名获取Class对象,立即加载并初始化类
getClass()通过对象实例获取Class对象,返回实际类型
getName()获取类的全限定名(含包名)
getSimpleName()获取类名(不含包名)
isXXX()判断类型(如isInterface()是否为接口,isEnum()是否为枚举)
getXXX()获取单个成员(如getField(String)获取 public 字段),含父类 public 成员
getXXXs()获取成员数组(如getMethods()获取所有 public 方法),含父类 public 成员
getDeclaredXXX()获取本类声明的单个成员(含私有),不含父类
getDeclaredXXXs()获取本类声明的所有成员(含私有),不含父类
newInstance()通过无参构造器创建实例(需类有默认构造器)

获取 Class 对象的三种方式

  1. 类名.class:通过类名直接获取,编译时确定类型。

  2. 对象.getClass () :通过实例对象获取,运行时确定实际类型。

  3. Class.forName (全限定类名) :通过类路径动态获取,适用于未知类型。

// 示例
public class ReflectionDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式1:类名.class
        Class<Student> clazz1 = Student.class;
        
        // 方式2:对象.getClass()
        Student student = new Student();
        Class<? extends Student> clazz2 = student.getClass();
        
        // 方式3:Class.forName()
        Class<?> clazz3 = Class.forName("com.example.Student");
    }
}

Constructor 类:操作构造方法

Constructor类用于获取和调用类的构造方法,支持访问私有构造器。

示例代码

public class Student {
    private String name;
    public int age;

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

// 反射操作构造器
public class TestConstructor {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.example.Student");
        
        // 调用无参构造器
        Student s1 = (Student) clazz.newInstance();
        
        // 调用public单参构造器
        Constructor<?> c1 = clazz.getConstructor(int.class);
        Student s2 = (Student) c1.newInstance(18);
        
        // 调用私有构造器(需设置可访问)
        Constructor<?> c2 = clazz.getDeclaredConstructor(String.class, int.class);
        c2.setAccessible(true); // 解除私有访问限制
        Student s3 = (Student) c2.newInstance("Tom", 20);
    }
}

Field 类:操作成员变量

Field类用于获取和修改类的成员变量,包括私有变量。

示例代码

public class TestField {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Student.class;
        Student student = new Student();
        
        // 获取public字段(含父类)
        Field ageField = clazz.getField("age");
        ageField.set(student, 20); // 修改public字段
        System.out.println(ageField.get(student)); // 输出:20
        
        // 获取私有字段(仅本类)
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true); // 解除私有限制
        nameField.set(student, "Jerry");
        System.out.println(nameField.get(student)); // 输出:Jerry
    }
}

Method 类:操作成员方法

Method类用于获取和调用类的成员方法,包括私有方法。

示例代码

public class Student {
    private void showName() {
        System.out.println("Name: " + name);
    }
    public int getAge() {
        return age;
    }
}

// 反射操作方法
public class TestMethod {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Student.class;
        Student student = new Student();
        
        // 调用私有方法
        Method showMethod = clazz.getDeclaredMethod("showName");
        showMethod.setAccessible(true);
        showMethod.invoke(student); // 输出:Name: Jerry
        
        // 调用public方法
        Method getMethod = clazz.getMethod("getAge");
        int age = (int) getMethod.invoke(student);
        System.out.println("Age: " + age); // 输出:20
    }
}

反射的实际应用:Spring IOC

Spring 的 IOC 容器核心依赖反射实现:

  1. 扫描类路径下的@Component注解类,通过Class.forName()获取Class对象。
  2. 解析类的属性,若存在@Autowired注解,通过Field类设置属性可访问(setAccessible(true))。
  3. 自动注入依赖的 Bean 实例,实现 “控制反转”。

总结

反射是 Java 动态编程的核心技术,通过操作ClassConstructorFieldMethod等类,可在运行时灵活操作类的成员。尽管反射会牺牲部分性能并打破封装性,但在框架开发(如 Spring、MyBatis)中不可或缺。掌握反射,能更深入理解 Java 的底层机制和框架原理。