Java反射机制

发布于 2026-07-29 10:55 更新于 2026-07-29 10:55 1624 字 9 min read ... 访问量

本文介绍了Java反射机制的核心概念与实际应用。反射允许程序在运行时动态获取类信息、创建对象、访问字段和调用方法,广泛用于框架、依赖注入和通用DAO等场景。文章通过示例说明了如何通过Class对象、Field、Method和Constructor实现动态操作,并指出通用DAO中反射的实现思路及局限性,同时强调了使用反射时需注意安全性、可读性与类型校验,避免直接使用用户输入作为类名或字段名。

Java 反射机制

反射概述

Java 反射是指程序在运行期间获取类的结构信息,并动态创建对象、访问字段、调用方法或调用构造器的机制。

通常情况下,代码在编译时就已经明确要使用的类型。使用反射后,程序可以在运行时根据类名或配置文件决定加载哪个类,因此反射常用于框架、依赖注入、对象映射和通用工具类。

类的加载与 Class 对象

JVM 加载一个类后,会为该类型创建唯一的 Class 对象。Class 对象保存了类名、字段、方法、构造器、注解等结构信息,是使用反射的入口。

获取 Class 对象

通过 Class.forName() 获取

try {
    Class<?> personClass = Class.forName("com.hyxy.Person");
    System.out.println(personClass.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

这种方式通过完整类名加载类,适合类名来自配置文件,或编译时不能直接引用目标类型的场景。

通过类字面量获取

Class<Person> personClass = Person.class;

编译时已经知道目标类型时,使用 类型.class 最直接,也不会抛出 ClassNotFoundException

通过对象获取

Person person = new Person();
Class<?> personClass = person.getClass();

已经持有对象,但需要获取其运行时类型时,可以调用 getClass()

public static void printType(Object object) {
    Class<?> objectClass = object.getClass();
    System.out.println(objectClass.getName());
}

Class 类的常用方法

方法作用
getName()获取完整类名,包括包名
getSimpleName()获取不包含包名的类名
getDeclaredField(String name)获取当前类中指定名称的字段
getDeclaredFields()获取当前类声明的全部字段
getDeclaredMethod(String name, Class<?>... parameterTypes)根据方法名和参数类型获取方法
getDeclaredMethods()获取当前类声明的全部方法
getDeclaredConstructor(Class<?>... parameterTypes)根据参数类型获取构造器
getDeclaredConstructors()获取当前类声明的全部构造器

Class.newInstance() 已经过时。创建对象时,应先获得构造器,再调用构造器的 newInstance()

Object object = personClass.getDeclaredConstructor().newInstance();

使用 Field 操作字段

每个 Field 对象代表类中声明的一个字段。通过 Field 可以获得字段名称、字段类型,并对指定对象的字段进行读取或赋值。

常用方法如下:

方法作用
getName()获取字段名
getType()获取字段类型
setAccessible(true)允许反射访问非公有字段
set(Object object, Object value)为指定对象的字段赋值
get(Object object)读取指定对象的字段值

下面的示例创建一个 Student 对象,并通过反射修改其 name 字段:

package com.hyxy;

import java.lang.reflect.Field;

public class FieldTest {
    public static void main(String[] args) {
        try {
            Class<?> studentClass = Class.forName("com.hyxy.Student");
            Object student = studentClass.getDeclaredConstructor().newInstance();

            Field nameField = studentClass.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(student, "Tom");

            Object name = nameField.get(student);
            System.out.println(name);
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

getDeclaredField() 能获取当前类声明的字段,包括私有字段,但不会自动向父类查找。访问私有字段时通常需要调用 setAccessible(true)

使用 Method 调用方法

每个 Method 对象代表类中的一个方法。调用 invoke() 时,第一个参数是方法所属的对象,后续参数是调用该方法需要传入的实参。

package com.hyxy;

import java.lang.reflect.Method;

public class MethodTest {
    public static void main(String[] args) {
        try {
            Class<?> studentClass = Class.forName("com.hyxy.Student");
            Object student = studentClass.getDeclaredConstructor().newInstance();

            Method method1 = studentClass.getDeclaredMethod("study");
            Method method2 = studentClass.getDeclaredMethod(
                    "study",
                    String.class,
                    int.class
            );

            method1.setAccessible(true);
            method2.setAccessible(true);

            method1.invoke(student);
            method2.invoke(student, "Java", 24);
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

查找重载方法时,不仅要提供方法名,还要提供准确的参数类型列表。调用方法过程中,目标方法内部抛出的异常会被包装为 InvocationTargetException

对于静态方法,调用 invoke() 时第一个参数可以传入 null

method.invoke(null, "参数");

使用 Constructor 创建对象

每个 Constructor 对象代表类中的一个构造器。通过参数类型可以获得指定构造器,再调用 newInstance() 创建对象。

package com.hyxy;

import java.lang.reflect.Constructor;

public class ConstructorTest {
    public static void main(String[] args) {
        try {
            Class<Student> studentClass = Student.class;
            Constructor<Student> constructor = studentClass.getDeclaredConstructor(
                    String.class,
                    int.class,
                    int.class
            );

            constructor.setAccessible(true);
            Student student = constructor.newInstance("Tom", 20, 110011);

            System.out.println(
                    student.getName() + "," +
                    student.getAge() + "," +
                    student.getNo()
            );
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

反射实现通用 DAO

基本思路

传统 DAO 通常为每张表分别编写增、删、改、查代码。通用 DAO 可以通过反射读取实体类的类名和字段信息,再动态生成 SQL。

一种简单的约定是:

  • 实体类名对应表名。
  • 实体类字段名对应列名。
  • 主键字段名由调用者传入。
  • 自增主键在插入时不作为占位符参数传入。

真实项目中,类名和表名、字段名和列名往往不完全相同,因此通常还会结合注解完成映射。

通用新增方法示例

package com.hyxy.dao;

import org.apache.commons.dbutils.QueryRunner;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class CommonDao {
    public int insert(Object entity, String primaryKeyName, boolean autoIncrement) {
        Class<?> entityClass = entity.getClass();
        String tableName = entityClass.getSimpleName();
        Field[] fields = entityClass.getDeclaredFields();

        List<String> columnNames = new ArrayList<>();
        List<String> placeholders = new ArrayList<>();
        List<Object> parameterValues = new ArrayList<>();

        for (Field field : fields) {
            String fieldName = field.getName();

            if (autoIncrement && fieldName.equals(primaryKeyName)) {
                continue;
            }

            field.setAccessible(true);
            columnNames.add(fieldName);
            placeholders.add("?");

            try {
                parameterValues.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("读取字段失败:" + fieldName, e);
            }
        }

        String sql = "insert into " + tableName +
                "(" + String.join(",", columnNames) + ") values(" +
                String.join(",", placeholders) + ")";

        QueryRunner queryRunner = new QueryRunner();

        try (Connection connection = DBUtil.getConnection()) {
            return queryRunner.update(
                    connection,
                    sql,
                    parameterValues.toArray()
            );
        } catch (SQLException e) {
            throw new RuntimeException("执行新增操作失败", e);
        }
    }
}

这个示例展示了反射和 JDBC 封装的基本组合方式,但仍有以下限制:

  • 实体类名必须与表名一致。
  • 字段名必须与列名一致。
  • 字段顺序依赖反射返回结果,不应将其作为数据库固定顺序使用。
  • 真实项目还需要处理主键、继承字段、静态字段、事务和异常体系。
  • 表名与列名不能使用预编译占位符,因此必须来自可信的映射信息,不能直接使用用户输入。

反射使用注意事项

反射提高了程序的动态性,但也会降低代码的可读性,并绕过部分编译期类型检查。使用时应遵循以下原则:

  • 能直接调用时,优先使用普通 Java 代码。
  • 对反射入口进行类型和参数校验。
  • 不要把未经校验的用户输入直接作为类名、方法名、表名或列名。
  • ReflectiveOperationException 进行统一处理。
  • 在模块化 Java 项目中,访问非公有成员还可能受到模块访问规则限制。

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka