Kotlin伴生对象实现单例时为何检测到存在公共构造函数
问题现象
我在结合设计模式学习Kotlin时,尝试编写单例模式实现,配套两组测试用例:一组验证单例实例全局唯一,一组验证单例类不存在公共构造函数,测试代码如下:
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.Test class TestSimpleSingleton { @Test fun testCreateSingleton() { val s = Singleton.Instance() val s2 = Singleton.Instance() assertSame(s, s2) } @Test fun testNoPublicConstructors() { val singleton = Class.forName("Singleton") val constructors = singleton.constructors assertEquals(0, constructors.size) // public constructor = 0 } }
两种实现的测试表现:
- 直接使用
object关键字实现单例(调用方式val s = Singleton)时,测试全部通过,实现代码:
object Singleton
- 使用
companion object(伴生对象)实现懒加载单例时,第二个测试失败,检测到类存在1个公共构造函数,实现代码:
class Singleton private constructor(){ companion object { private var theInstance: Singleton? = null fun Instance(): Singleton { if (theInstance == null) theInstance = Singleton() return theInstance as Singleton } } }
我已经将类的主构造函数明确声明为private,理论上不应存在公共构造函数。
产生原因
这个问题是JVM平台上Kotlin编译器的合成构造函数生成规则导致的:
- 你定义的
private constructor()无参主构造,编译后确实是private访问权限,正常业务代码无法直接调用。 - 当类中存在伴生对象时,Kotlin编译器会额外生成一个标记为*synthetic(合成)*的public构造函数,参数是编译器内部使用的
DefaultConstructorMarker类型。这个构造是给编译器内部逻辑调用的,普通代码无法直接调用,但它的访问权限是public,因此会被JDK反射的getConstructors()方法(也就是你代码中调用的constructors扩展属性的底层实现)识别到,统计为1个公共构造。 - 而用
object关键字声明的单例类,编译器不会生成这个额外的合成public构造,只有一个private的无参构造,因此反射统计到的公共构造数量为0,测试可以通过。
修正方案
如果要准确判断类不存在用户可调用的公共构造函数,需要在反射统计时过滤掉编译器生成的合成构造,修改测试代码如下:
@Test fun testNoPublicConstructors() { val singleton = Class.forName("Singleton") val constructors = singleton.constructors.filter { !it.isSynthetic } // 过滤编译器生成的合成构造 assertEquals(0, constructors.size) }
另外补充:你当前手写的懒汉单例是线程不安全的,多线程并发场景下可能生成多个实例,Kotlin中推荐用by lazy委托实现线程安全的懒加载单例,代码更简洁:
class Singleton private constructor(){ companion object { val Instance: Singleton by lazy { Singleton() } } }
内容的提问来源于stack exchange,提问作者blahblah
相关产品推荐
相关产品推荐

