本文将结合完整的Java代码案例,从基础概念到实操细节,全面拆解Java反射机制的核心用法。内容涵盖Class对象获取、字段/构造器/方法的反射操作、暴力反射原理及避坑指南,适合Java初学者及需要巩固反射知识点的开发者。

一、反射机制核心概念

反射是Java动态性的核心体现,其核心价值在于:允许程序在运行时而非编译期,获取类的完整结构信息(包括字段、方法、构造器等),并能直接操作类的私有成员,打破了Java的封装限制。

反射的核心入口是 java.lang.Class 类——每个类在JVM中仅存在一个Class对象,它是访问类结构的“钥匙”。

1.1 获取Class对象的3种方式

日常开发中最常用第一种方式(全类名加载),后两种多用于已知类或对象的场景:

// 方式1:通过全类名加载(最常用,动态性最强)
Class clazz = Class.forName("com.qcby1.User");

// 方式2:通过类名.class(编译期确定,静态方式)
Class clazz2 = User.class;

// 方式3:通过对象.getClass()(需先创建对象,灵活性低)
User user = new User();
Class clazz3 = user.getClass();

二、核心实操:反射操作字段(Field)

字段反射是反射机制中最常用的场景之一,核心是区分 getDeclaredFields()getFields() 的差异,以及私有字段的访问方式。

2.1 先看基础:User类字段定义

package com.qcby1;

public class User {
    private String name = "zs";       // 私有字段
    private String sex = "男";        // 私有字段
    public Integer age = 18;          // 公共字段
    double height = 183;              // 默认访问权限字段
    protected String phone = "123456";// 受保护字段
}

2.2 获取字段的2类核心方法

方法

作用

是否包含私有成员

是否包含父类成员

getDeclaredFields()

获取当前类的所有字段

getFields()

获取当前类及父类的公共字段(public)

2.3 代码案例:获取并操作字段

package com.qcby1;

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception {
        // 1. 获取Class对象
        Class clazz = Class.forName("com.qcby1.User");
        // 2. 获取无参构造器,创建对象(后续操作字段需用到实例)
        User user = (User) clazz.getConstructor().newInstance();
        
        // 3. 获取所有字段(含私有)并打印
        Field[] allFields = clazz.getDeclaredFields();
        System.out.println("所有字段:");
        for (Field field : allFields) {
            System.out.println(field);
        }
        
        // 4. 获取公共字段(仅public)并打印
        Field[] publicFields = clazz.getFields();
        System.out.println("\n公共字段:");
        for (Field field : publicFields) {
            System.out.println(field);
        }
        
        // 5. 操作指定字段:私有字段(name)
        Field nameField = clazz.getDeclaredField("name");
        // 关键:私有/受保护字段需开启暴力反射,关闭权限检查
        nameField.setAccessible(true);
        nameField.set(user, "ls"); // 赋值
        System.out.println("\n私有字段name的值:" + nameField.get(user)); // 取值
        
        // 6. 操作指定字段:公共字段(age)
        Field ageField = clazz.getField("age");
        ageField.set(user, 20); // 公共字段无需暴力反射
        System.out.println("公共字段age的值:" + ageField.get(user));
        
        // 7. 操作指定字段:受保护字段(phone)
        Field phoneField = clazz.getDeclaredField("phone");
        phoneField.setAccessible(true); // 受保护字段也需暴力反射
        phoneField.set(user, "18999999999");
        System.out.println("受保护字段phone的值:" + phoneField.get(user));
    }
}

2.4 核心要点

a.获取指定字段用 getDeclaredField("字段名")(任意权限)或 getField("字段名")(仅public)

b.非public字段(private/protected/默认)必须通过 setAccessible(true) 开启“暴力反射”,否则抛 IllegalAccessException

c.setAccessible(true) 仅在运行时临时关闭权限检查,不会修改类的原始访问修饰符。

三、反射操作构造器(Constructor)

构造器的反射核心是:通过“参数列表”区分重载的构造器(构造器名与类名一致,无法通过名称区分)。

3.1 核心方法对比

方法

作用

是否包含私有构造器

getDeclaredConstructors()

获取当前类的所有构造器

getConstructors()

获取当前类的公共构造器(public)

3.2 代码案例:获取构造器并创建对象

package com.qcby1;

import java.lang.reflect.Constructor;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.qcby1.User");
        
        // 1. 获取所有构造器(含私有)
        Constructor[] allConstructors = clazz.getDeclaredConstructors();
        System.out.println("所有构造器:");
        for (Constructor constructor : allConstructors) {
            System.out.println(constructor);
        }
        
        // 2. 获取无参构造器(public)并创建对象
        Constructor publicNoArgConstructor = clazz.getConstructor();
        User user1 = (User) publicNoArgConstructor.newInstance();
        System.out.println("\n无参构造器创建的对象:" + user1);
        
        // 3. 获取带参数的私有构造器(假设User有private User(String name))
        Constructor privateConstructor = clazz.getDeclaredConstructor(String.class);
        privateConstructor.setAccessible(true); // 私有构造器需暴力反射
        User user2 = (User) privateConstructor.newInstance("ls");
        System.out.println("私有构造器创建的对象:" + user2);
        
        // 4. 获取带多个参数的构造器(假设User有User(Integer age, String sex))
        Constructor multiParamConstructor = clazz.getDeclaredConstructor(Integer.class, String.class);
        User user3 = (User) multiParamConstructor.newInstance(18, "女");
        System.out.println("多参数构造器创建的对象:" + user3);
    }
}

3.3 核心要点

a.构造器的唯一标识是“参数列表”(方法签名),获取指定构造器时必须传入参数类型(如 getDeclaredConstructor(String.class));

b.通过 newInstance(参数值) 调用构造器创建对象,参数顺序需与构造器定义一致;

c.私有构造器必须开启暴力反射才能调用,否则抛IllegalAccessException

四、反射操作方法(Method)

方法反射的核心是“方法签名”(方法名+参数列表),这是区分重载方法的关键,操作流程与字段、构造器类似。

4.1 先看基础:User类方法定义

package com.qcby1;

public class User {
    // 重载方法:方法名相同,参数列表不同
    public void run(String name) {
        System.out.println("公共带参run方法:" + name);
    }
    private void run() {
        System.out.println("私有无参run方法");
    }
    void flay(Integer age, String phone) { // 默认权限
        System.out.println("默认权限flay方法:" + age + "," + phone);
    }
    protected void jump() { // 受保护权限
        System.out.println("受保护jump方法");
    }
    public void jump(String name) { // 重载方法
        System.out.println("公共带参jump方法:" + name);
    }
}

4.2 核心方法对比

方法

作用

是否包含私有方法

是否包含父类方法

getDeclaredMethods()

获取当前类的所有方法

getMethods()

获取当前类及父类的公共方法(public)

是(如Object的toString())

4.3 代码案例:获取并调用方法

package com.qcby1;

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("com.qcby1.User");
        User user = (User) clazz.getConstructor().newInstance();
        
        // 1. 获取所有方法(含私有)
        Method[] allMethods = clazz.getDeclaredMethods();
        System.out.println("所有方法:");
        for (Method method : allMethods) {
            System.out.println(method);
        }
        
        // 2. 调用公共带参run方法(public void run(String name))
        Method runWithParam = clazz.getDeclaredMethod("run", String.class);
        runWithParam.invoke(user, "张三"); // 传入对象和方法参数
        
        // 3. 调用私有无参run方法(private void run())
        Method runNoParam = clazz.getDeclaredMethod("run");
        runNoParam.setAccessible(true); // 私有方法需暴力反射
        runNoParam.invoke(user); // 无参方法无需传参数
        
        // 4. 调用默认权限flay方法(void flay(Integer, String))
        Method flayMethod = clazz.getDeclaredMethod("flay", Integer.class, String.class);
        flayMethod.setAccessible(true); // 默认权限也需暴力反射
        flayMethod.invoke(user, 18, "123456");
        
        // 5. 调用重载的jump方法
        Method jumpNoParam = clazz.getDeclaredMethod("jump");
        jumpNoParam.setAccessible(true);
        jumpNoParam.invoke(user);
        
        Method jumpWithParam = clazz.getDeclaredMethod("jump", String.class);
        jumpWithParam.invoke(user, "李四");
    }
}

4.4 核心要点

a.获取指定方法时,必须传入“方法名+参数类型”(如 getDeclaredMethod("run", String.class)),否则无法区分重载方法;

b.通过 invoke(对象, 参数值) 调用方法,无参方法可省略参数或传null;

c.非public方法(private/protected/默认)需开启暴力反射,否则抛 IllegalAccessException

d.getMethods() 会获取父类的public方法(如Object类的equals()、hashCode()),而 getDeclaredMethods() 不会。

五、新手必避的3个坑

  1. 用getField()/getMethod()获取私有成员:直接抛 NoSuchFieldExceptionNoSuchMethodException,需用 getDeclaredXxx()

  2. 操作非public成员未开启暴力反射:抛 IllegalAccessException,记住:只要不是public,就需要 setAccessible(true)

  3. 获取重载方法未指定参数类型:抛 NoSuchMethodException,重载方法的核心是参数列表,必须明确指定。

六、总结

Java反射的核心逻辑可总结为3句话:

1.反射的入口是Class对象,通过 Class.forName(全类名) 动态加载类;

2.getDeclaredXxx() 获取所有成员(含私有),getXxx() 仅获取public成员(含父类);

3.操作非public成员必须开启暴力反射(setAccessible(true)),构造器和方法的唯一标识是参数列表。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐