You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin如何实现类对外可见但构造函数仅模块内可访问

问题原因
  • Kotlin的internal是编译期生效的模块级访问限制,JVM字节码规范没有对应的原生访问修饰符,默认仅在Kotlin代码跨模块编译时做访问校验,不会在字节码层面直接阻止调用。
  • 给带默认参数的构造函数加internal修饰时,Kotlin为了实现默认参数逻辑自动生成的合成构造、无参重载构造不会同步继承internal可见性,会被生成为public,直接导致外部代码(尤其是Java代码)可以绕过Kotlin编译器检查直接实例化类。
可行实现方案

方案1:私有主构造 + 模块内可见创建入口(最可靠,无可见性泄漏)

将类的主构造声明为private,从字节码层面彻底禁止外部直接调用构造实例,再在模块内提供构造入口,既可以保留直接构造的调用体验,也可以用工厂方法实现。

1.1 保留原生构造调用体验

通过给私有主构造增加一个无意义的私有标记参数避免签名冲突,再编写对应签名的internal次级构造供模块内调用,如果需要阻止Java代码绕过Kotlin编译器访问,给次级构造加上*@JvmSynthetic*注解即可:

class UserInformationException private constructor(
    msg: String,
    // 仅用于区分签名的私有标记参数,外部无法传值
    private val marker: Nothing?
) : Exception(msg) {
    // 模块内可直接调用的带自定义消息构造
    @JvmSynthetic
    internal constructor(msg: String) : this(msg, null)
    // 模块内可直接调用的默认消息构造
    @JvmSynthetic
    internal constructor() : this(ERROR_MESSAGE, null)

    companion object {
        private const val ERROR_MESSAGE = "User information wrong!!"
    }
}

模块内调用方式和普通类完全一致:

val e1 = UserInformationException()
val e2 = UserInformationException("自定义错误信息")

1.2 工厂方法写法

如果不介意调用方式稍作调整,可以直接在伴生对象里定义internal修饰的工厂方法,代码更简洁,不存在签名冲突问题:

class UserInformationException private constructor(msg: String) : Exception(msg) {
    companion object {
        private const val ERROR_MESSAGE = "User information wrong!!"

        @JvmSynthetic
        internal fun create(msg: String = ERROR_MESSAGE): UserInformationException {
            return UserInformationException(msg)
        }
    }
}

模块内调用:

val e1 = UserInformationException.create()
val e2 = UserInformationException.create("自定义错误信息")

方案2:显式声明所有构造重载 + @JvmSynthetic注解

如果不想修改构造的调用体验,也不想加私有标记参数,就不要在主构造上使用默认参数,显式写出所有重载的构造函数,给每个构造都加上internal修饰和*@JvmSynthetic*注解:

class UserInformationException @JvmSynthetic internal constructor(msg: String) : Exception(msg) {
    @JvmSynthetic
    internal constructor() : this(ERROR_MESSAGE)

    companion object {
        private const val ERROR_MESSAGE = "User information wrong!!"
    }
}

注意:这种方案绝对不能在构造参数上使用默认值,否则Kotlin自动生成的合成默认参数构造不会被加上*@JvmSynthetic*和internal限制,仍然会出现public构造泄漏的问题。

方案选择建议
  • 如果追求绝对的访问限制,完全杜绝外部(反射除外)实例化的可能,优先选方案1的私有主构造实现,JVM层面的private修饰是强约束,不会随Kotlin编译器版本变化出现可见性漏洞。
  • 如果类构造逻辑简单、重载数量少,选方案2即可,注意不要使用默认参数,显式声明所有构造重载。

内容的提问来源于stack exchange,提问作者lannyf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 17:01:27