广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Java反射如何获取类的属性和方法?基础步骤详解

反射机制让Java程序在运行期间也能查看类的内部结构,这在框架开发、注解处理和对象序列化等场景中非常常见。要获取一个类的属性和方法,第一步不是直接调用某个API,而是先拿到代表该类的Class对象。Class对象是JVM为每个已加载类维护的类型信息入口,后续对字段、构造器、方法的查询都从这里发起。如果拿不到Class对象,反射的一切操作都无从谈起。

Java反射如何获取类的属性和方法?基础步骤详解

下面从Class对象的获取开始,逐步拆解获取属性与方法的具体步骤,并给出可以运行的代码示例。

一、先拿到Class对象:反射操作的入口

Class对象的获取有三种常用方式。第一种是使用类字面量,例如Person.class。这种方式在编译期就确定了类型,不会抛出受检异常,执行效率较高,适合已经明确知道类名的情况。第二种是通过对象的getClass方法,任何Object都有这个方法,它返回对象实际运行时类的Class实例。第三种是使用Class.forName静态方法,传入类的全限定名,例如Class.forName("com.learning.reflect.Person")。这种方式最灵活,类名可以由配置文件或用户输入决定,但需要处理ClassNotFoundException。

三种方式各有适用场景。类字面量适合写死的工具类;getClass适合在方法内部根据传入对象动态判断;Class.forName适合加载驱动、解析XML配置等场景。下面的代码展示了三种获取方式:

public class ClassDemo {
    public static void main(String[] args) throws Exception {
        // 方式一:类字面量
        Class clazz1 = Person.class;
        // 方式二:对象的 getClass 方法
        Person person = new Person();
        Class clazz2 = person.getClass();
        // 方式三:Class.forName 加载全限定名
        Class clazz3 = Class.forName("com.learning.reflect.Person");
        System.out.println(clazz1 == clazz2); // true
        System.out.println(clazz2 == clazz3); // true
    }
}

同一个类无论通过哪种方式获取,在同一个类加载器下返回的Class对象都是同一个实例,因此上面的比较结果为true。这也说明Class对象不是轻量级的数据副本,而是JVM内部真实类型信息的映射。理解这一点有助于后续操作,因为对任意一个Class对象获取到的字段和方法都代表同一份元数据。

二、获取类的属性:Field对象的遍历与访问

拿到Class对象以后,获取属性主要通过getDeclaredFields和getFields两个方法。getDeclaredFields返回当前类中声明的所有字段,不区分public、protected、private,但不包含继承自父类的字段。getFields则只返回public字段,并且包含从父类继承来的public字段。这个差异很容易造成排查困难:如果只想查看本类自己的字段,应该优先使用getDeclaredFields;如果需要对外暴露的公共成员,可以使用getFields。

下面的代码定义了一个带有私有字段的Person类,然后通过反射遍历它的所有字段:

import java.lang.reflect.Field;
public class FieldDemo {
    public static void main(String[] args) {
        Class clazz = Person.class;
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("字段名:" + field.getName());
            System.out.println("字段类型:" + field.getType().getName());
            System.out.println("修饰符:" + field.getModifiers());
        }
    }
}
class Person {
    private String name;
    private int age;
    public String address;
}

从输出中可以看到,私有字段name和age都会被列出。Field类提供了多个实用方法:getName返回字段名称,getType返回字段的Class对象,getModifiers返回修饰符的整数表示,可以配合Modifier.toString方法转换为可读文本。如果需要读取或修改字段值,还可以使用get和set方法。但私有字段默认不允许直接访问,必须调用field.setAccessible(true)关闭访问检查,否则会抛出IllegalAccessException。

下面展示如何修改一个私有字段的值:

import java.lang.reflect.Field;
public class ModifyFieldDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        Class clazz = person.getClass();
        Field nameField = clazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(person, "张三");
        Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(person, 25);
        System.out.println(person.getName()); // 输出:张三
        System.out.println(person.getAge());  // 输出:25
    }
}

这里使用getDeclaredField按名称查找单个字段,比遍历数组更直接。setAccessible(true)会绕过Java访问控制,这在很多框架中用于注入依赖或反序列化数据,但也意味着封装性被打破,使用时需要谨慎。对于基本类型字段,Field提供了setInt、setBoolean等类型安全的重载方法,也可以统一使用set方法并传入包装类型。

三、获取类的方法:Method对象的查找与调用

获取方法与获取字段的思路类似,但方法更复杂,因为方法包含名称、参数列表、返回类型和异常声明。Class类提供了getDeclaredMethods和getMethods两个方法。getDeclaredMethods返回当前类声明的所有方法,包括私有方法,但不包含继承的方法;getMethods返回所有public方法,包括从父类继承的方法,但不包含私有方法。开发中如果只关心本类自己定义的方法,使用getDeclaredMethods即可。

遍历方法时,Method对象可以输出方法名、参数类型和返回类型。下面的示例展示如何打印一个类的全部方法签名:

import java.lang.reflect.Method;
public class MethodListDemo {
    public static void main(String[] args) {
        Class clazz = Person.class;
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            StringBuilder builder = new StringBuilder();
            builder.append(method.getReturnType().getSimpleName());
            builder.append(" ");
            builder.append(method.getName());
            builder.append("(");
            Class[] params = method.getParameterTypes();
            for (int i = 0; i < params.length; i++) {
                if (i > 0) {
                    builder.append(", ");
                }
                builder.append(params[i].getSimpleName());
            }
            builder.append(")");
            System.out.println(builder.toString());
        }
    }
}

代码中使用了StringBuilder拼接方法签名,注意循环中对于参数分隔符的处理。getParameterTypes返回参数类型的Class数组,getReturnType返回返回类型。如果要获取指定名称和参数列表的方法,可以使用getDeclaredMethod或getMethod,并传入参数类型的Class数组。例如Person类中有一个私有方法sayHello,可以这样获取并调用:

import java.lang.reflect.Method;
public class InvokeMethodDemo {
    public static void main(String[] args) throws Exception {
        Person person = new Person();
        Class clazz = person.getClass();
        Method method = clazz.getDeclaredMethod("sayHello");
        method.setAccessible(true);
        method.invoke(person);
    }
}

invoke方法第一个参数是要调用方法的对象,如果是静态方法则可以传null。如果方法有参数,invoke后面按顺序传入对应参数值。私有方法同样需要setAccessible(true)才能调用。与字段访问一样,这种方式绕过了访问控制,适合测试或框架场景,但不应在业务代码中随意使用。

四、完整示例:同时获取属性和方法并调用

为了让流程更完整,下面给出一个综合示例。先定义一个包含私有字段、构造器、普通方法和私有方法的User类,然后通过反射获取字段并修改,再获取私有方法并调用。整个过程覆盖了反射获取属性和方法的核心步骤。

import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectFullDemo {
    public static void main(String[] args) throws Exception {
        Class clazz = User.class;
        // 创建对象
        Object user = clazz.getDeclaredConstructor().newInstance();
        // 获取并修改私有字段
        Field usernameField = clazz.getDeclaredField("username");
        usernameField.setAccessible(true);
        usernameField.set(user, "李四");
        Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(user, 30);
        // 获取并调用私有方法
        Method showMethod = clazz.getDeclaredMethod("showInfo");
        showMethod.setAccessible(true);
        showMethod.invoke(user);
    }
}
class User {
    private String username;
    private int age;
    public User() {}
    private void showInfo() {
        System.out.println("用户名:" + username + ",年龄:" + age);
    }
}

运行这个示例会输出“用户名:李四,年龄:30”。这里使用了getDeclaredConstructor().newInstance()来创建对象,这是反射创建实例的常用方式。对于私有字段和方法,都调用了setAccessible(true)。整个过程可以概括为:获取Class对象、获取Field或Method对象、关闭访问检查、执行读取或调用操作。每个步骤都对应一个明确的反射API,掌握这个路径后,处理更复杂的反射需求也会更加顺手。

反射虽然灵活,但它绕过了编译期类型检查,代码的可读性和性能都会受到一定影响。在实际业务中,反射更适合框架底层、工具类或需要动态加载类的场景。如果只是普通业务代码,建议优先使用直接的字段访问和方法调用,只有在确实需要运行时动态处理时才引入反射。

广告位 · 文章底部(后台-模块管理替换此区域)