Java中Constructor.newInstance调用私有构造器在JDK 9+会抛IllegalAccessException,因模块系统拦截;需配合--add-opens参数并调用setAccessible(true),或改用Unsafe.allocateInstance绕过构造逻辑。

Constructor.newInstance 调用私有构造方法会抛 IllegalAccessException
直接调用 Constructor.newInstance 无法绕过 Java 的访问控制——哪怕你已经用 setAccessible(true),JDK 9+ 默认仍会拒绝私有构造器的反射实例化,报错:java.lang.IllegalAccessException: class is not public。这不是权限没设对,而是模块系统(Module System)在拦截。
- JDK 8 及以前:设
setAccessible(true)就能用newInstance() - JDK 9+:必须额外打开模块封装,否则
setAccessible(true)失效 - 更关键的是:
Constructor.newInstance已被标记为@Deprecated(since="9"),官方推荐改用Constructor.newInstantiator(但该方法仅限内部使用),实际应转向Unsafe.allocateInstance或保持兼容性写法
JDK 9+ 正确做法:先 open module,再 setAccessible
核心是让运行时允许反射穿透模块边界。以构造 java.util.ArrayList 的私有构造器(如 ArrayList(Collection))为例,需确保 java.base 模块对当前类所在模块“开放”反射权限。
启动 JVM 时加参数(最稳妥):
--add-opens java.base/java.util=ALL-UNNAMED
或(若用模块化项目):
--add-opens java.base/java.util=your.module.name
代码中仍需:
- 用
getDeclaredConstructor(...)获取私有构造器 - 显式调用
constructor.setAccessible(true) - 再调
constructor.newInstance(...)
替代方案:用 Unsafe.allocateInstance 绕过构造逻辑
如果你只是想获得一个对象实例、且不关心构造器是否执行(比如跳过初始化逻辑、避免副作用),Unsafe.allocateInstance 是更底层也更稳定的选项——它不调用任何构造方法,包括私有构造器,也不受模块限制。
- 获取
Unsafe实例需反射(因单例字段是私有的):Unsafe.getUnsafe()在 JDK 9+ 对应用类加载器不可见,得走theUnsafe字段 -
allocateInstance不执行构造函数,字段全为默认值(null/0/false) - 适合 mock、序列化反解析、对象池等场景,但不能用于依赖构造器初始化状态的类
为什么不用 ReflectionFactory.newConstructorAccessor
这是 HotSpot 内部优化路径,Constructor.newInstance 底层其实就用了它。但它是非公开 API(sun.reflect 包),JDK 16+ 默认禁止访问,即使加 --add-exports 也极不稳定,不同 JVM 厂商实现可能不一致。
真实项目里硬依赖它,等于把维护成本锁死在某几个 JDK 版本上。遇到 NoClassDefFoundError 或 InaccessibleObjectException 就只能退回去修启动参数或换方案。
模块边界和构造器语义是强耦合的,漏掉 --add-opens 或误判构造器可见性,是线上反射失败最常见的原因。











