导读:本期聚焦于三上悠亚创作的《如何在 Java 中通过 Class.getDeclaredConstructor() 利用反射获取并调用非公开的构造函数》,敬请观看详情。在Java开发中,有时我们需要访问类的非公开构造函数来完成特殊场景的对象创建,比如框架开发、序列化反序列化等场景。反射机制提供了这样的能力,其中Class.getDeclaredConstructor()方法是获取非公开构造函数的核心入口。本文将详细介绍该方法的用法,包括如何获取指定参数类型的构造函数、如何设置访问权限绕过私有限制、如何传递参数完成构造函数调用,同时会说明使用过程中的常见注意事项,帮助开发者正确掌握反射调用非公开构造函数的完整流程,避免常见的权限异常和参数匹配错误问题。

认识反射中的构造函数获取机制

反射机制赋予Java程序在运行时透视自身结构的能力,通过它,开发者可以动态地获取类的方法、字段以及构造函数信息。在构造函数这一维度,Class类提供了两种常用的获取方法:getConstructor()getDeclaredConstructor()。二者名字相似,行为却有本质差异。getConstructor()只能返回当前类中声明为public的构造函数,而getDeclaredConstructor()则能返回当前类中声明的所有构造函数,无论其访问修饰符是privateprotected还是包级私有(默认权限)。这意味着,借助getDeclaredConstructor(),我们可以拿到那些通常无法从外部直接访问的构造器,并进一步利用它创建对象实例。

Java反射获取构造函数示意图

在很多设计模式或框架场景中,例如单例模式的破解、依赖注入容器、对象池等,经常需要绕开访问控制来实例化某些类。虽然这种操作打破了封装原则,但在合理的业务背景下,它确实提供了一种灵活且强大的动态扩展手段。理解并掌握getDeclaredConstructor()的使用流程,能帮助我们更深入地理解Java反射体系,也能够在必要时写出更通用的工具代码。

getDeclaredConstructor与getConstructor的区别

在开始编码之前,有必要理清这两个方法的应用边界。getConstructor()返回的是与参数类型列表匹配的公共构造函数,也就是说,它只搜索那些用public关键字修饰的构造器。如果试图使用该方法获取一个私有构造函数,运行时会抛出NoSuchMethodException,因为公共构造器集合中并不包含该成员。

getDeclaredConstructor()则不受访问修饰符的限制。它直接针对当前类的声明体进行检索,因此不管是私有的、受保护的、默认权限的,还是公共的构造函数,只要参数类型匹配,都会被返回。从这个角度看,getDeclaredConstructor()是更底层的反射入口,它赋予调用者操作非公开成员的能力,但同时也要求调用者对访问权限控制负有额外责任。

需要特别说明的是,getDeclaredConstructor()仅能获取“当前类里声明”的构造函数,而不会去父类中查找构造函数。这与getConstructor()的行为类似,构造函数不像普通方法那样会继承。因此,在定位目标构造器时,必须确保它确实定义在操作的这个类中。

利用getDeclaredConstructor调用非公开构造函数

要完成对非公开构造函数的调用,通常包含四个核心步骤:获取Class对象、定位目标构造函数、解除访问限制、创建实例。每一步都有对应的API和注意事项。下面通过一个具体的例子来展示完整的操作流程。

定义包含非公开构造函数的测试类

首先,创建一个名为User的类,其中包含一个private修饰的构造函数和一个默认访问权限的构造函数,这样我们可以分别测试不同访问级别的场景。类内部保存姓名和年龄两个字段,并重写toString()方法便于打印输出。

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

    // 私有的构造函数
    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 默认访问权限的构造函数
    User(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

获取目标类的Class对象

反射操作的起点是获得目标类型的Class实例。常见的方式包括:使用类名.class、调用对象的getClass()方法,或者通过Class.forName("完整类名")动态加载。这里选择最直观的User.class写法,它不会触发类初始化,适合大多数反射场景。

// 获取User类的Class对象
Class<User> userClass = User.class;

定位目标构造函数

使用getDeclaredConstructor()时,需要传入一个类型列表,用以精确匹配构造函数的形参列表。以私有构造函数User(String, int)为例,必须传入String.classint.class。这里的顺序、基本类型与引用类型都必须完全一致,否则方法会抛出NoSuchMethodException

try {
    // 获取私有构造函数,参数类型为String和int
    Constructor<User> privateConstructor =
            userClass.getDeclaredConstructor(String.class, int.class);
    System.out.println("成功获取私有构造函数:" + privateConstructor);
} catch (NoSuchMethodException e) {
    System.out.println("未找到匹配的构造函数");
    e.printStackTrace();
}

解除访问权限限制

即使成功拿到了Constructor对象,直接调用其newInstance()方法仍然可能失败,因为非公共构造函数在默认情况下不具备外部可调用性。此时需要调用Constructor.setAccessible(true)来绕过Java语言的访问控制检查。这一操作会修改虚拟机对访问权限的判定,让后续的实例化调用能够顺利进行。

// 设置构造函数可访问,绕过private限制
privateConstructor.setAccessible(true);

创建对象实例

权限解除后,就可以通过Constructor.newInstance(Object... initargs)传入构造参数,完成对象创建。该方法接收一个可变参数列表,参数的数量和类型必须与构造函数定义相匹配。如果构造函数运行期间抛出异常,则会被包装成InvocationTargetException抛出,这一点需要特别留意。

try {
    // 传入构造函数的参数,创建User对象
    User user = privateConstructor.newInstance("张三", 20);
    System.out.println("创建的对象:" + user);
} catch (Exception e) {
    e.printStackTrace();
}

默认访问权限构造函数的调用细节

对于包级私有的默认访问权限构造函数,在同一个包内使用时,理论上不需要调用setAccessible(true)。因为默认访问权限允许同包内的类进行访问,反射机制在这一前提下能够直接调用。但一旦目标类位于不同的包中,默认权限构造函数就变得不可访问,此时同样需要借助setAccessible(true)来解除限制。

为了保持代码的健壮性并简化逻辑,在实际开发中通常对所有通过反射获取的非公共构造函数统一调用setAccessible(true)。这样既能避免因包路径变化导致的访问问题,也能让调用逻辑更加一致。下面的代码演示了获取默认权限构造函数的完整过程。

try {
    // 获取默认访问权限的构造函数,参数为String
    Constructor<User> defaultConstructor =
            userClass.getDeclaredConstructor(String.class);
    // 同一包下可以不设置,但建议统一设置以避免跨包问题
    defaultConstructor.setAccessible(true);
    User user2 = defaultConstructor.newInstance("李四");
    System.out.println("创建的对象:" + user2);
} catch (Exception e) {
    e.printStackTrace();
}

反射调用中的常见问题与处理建议

非公共构造函数的反射调用并非总是一帆风顺,开发者需要关注以下几个关键点,才能减少运行时错误并避免潜在的副作用。

  • 参数类型必须精确匹配getDeclaredConstructor()执行的是严格匹配,它不会进行自动装箱或类型提升。例如,构造函数形参类型是int,那么调用时必须传入int.class,传入Integer.class会匹配失败并抛出NoSuchMethodException。同理,形参是接口类型时,也必须传入该接口的Class对象,而不是其实现类。
  • 异常类型需要分类处理:反射操作可能抛出多种受检查异常,包括NoSuchMethodException(没有匹配的构造函数)、IllegalAccessException(访问权限不足)、InstantiationException(目标类是抽象类或接口导致无法实例化)、InvocationTargetException(构造函数内部抛出异常,被包装为触发异常)。捕获异常时,建议分别处理或使用Exception统一接收后再做具体判断。
  • 访问权限状态应及时恢复:如果只是临时调用某个非公开构造函数,在完成对象创建后,可以调用setAccessible(false)将构造器恢复到初始状态。这样做可以避免访问控制状态被意外修改,特别是在长期运行的服务中,保持反射调用的“清洁性”有助于降低安全风险。
  • 性能开销需要权衡:反射调用比直接调用构造函数的性能低得多。这是因为反射需要动态解析方法、检查访问权限、处理参数包装等。在高频场景下,应优先考虑使用构造函数引用或依赖注入容器来替代反射。只有在运行频率极低的工具方法中,才能接受它带来的额外开销。

完整可运行示例

为了便于读者直观理解,下面给出一个完整的Java程序,它同时演示了私有构造函数和默认访问权限构造函数在反射机制下的获取与调用。程序中包含异常处理与结果输出,可以直接复制到开发环境中运行。

import java.lang.reflect.Constructor;

public class ReflectConstructorDemo {
    public static void main(String[] args) {
        Class<User> userClass = User.class;
        try {
            // 获取并调用私有构造函数
            Constructor<User> privateConstructor =
                    userClass.getDeclaredConstructor(String.class, int.class);
            privateConstructor.setAccessible(true);
            User user1 = privateConstructor.newInstance("张三", 20);
            System.out.println(user1);

            // 获取并调用默认访问权限构造函数
            Constructor<User> defaultConstructor =
                    userClass.getDeclaredConstructor(String.class);
            defaultConstructor.setAccessible(true);
            User user2 = defaultConstructor.newInstance("李四");
            System.out.println(user2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class User {
    private String name;
    private int age;

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

    User(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

反射是一把双刃剑,它在带来灵活性的同时,也要求开发者对代码的健壮性和安全性承担更多责任。使用getDeclaredConstructor()获取并调用非公开构造函数,是理解Java反射深层次特性的重要实践。在业务代码中,应尽量避免大面积使用这种技术,而在框架设计或工具类开发中,则可以将其作为解决问题的基础手段。掌握好访问权限的解除与恢复、异常的正确处理以及性能上的取舍,就能让反射在关键位置发挥出不可替代的价值。

Java反射Class.getDeclaredConstructor非公开构造函数Constructor类修改时间:2026-07-08 13:00:36

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。