为什么Kotlin不使用静态内部类来实现带有“object”关键字的单例?

new9mtju  于 2023-03-24  发布在  Kotlin
关注(0)|答案(1)|浏览(98)

下面是一个用“object”关键字修饰的类A:

object A {
}

在将其反编译成Java代码后,发现它是通过静态代码块初始化的:

public final class A {
   public static final A INSTANCE;

   private A() {
   }

   static {
      A var0 = new A();
      INSTANCE = var0;
   }
}

我很好奇为什么Kotlin不使用静态内部类来实现单例,就像这样:

public final class A {
    public A getInstance() {
        return LazyHolder.INSTANCE;
    }

    private A() {
    }

    private static class LazyHolder {
        private static final A INSTANCE = new A();
    }
}

然后,当在类A中使用变量“b”时,在A和getB()之间添加“getInstance()”方法:

public static final void main() {
    A.getInstance().getB();
}

这种单例模式支持延迟加载,可以避免内存浪费,为什么Kotlin不使用这种方法来实现“object”关键字?

v64noz0r

v64noz0r1#

当前的行为基本上与您的建议相同,假设您在对象中没有任何@JvmStatic/@JvmField
所有的初始化都是在静态初始化器中完成的,它
当类被 * 初始化 * 时执行
并且类T被初始化,
在以下任何一种情况首次发生之前:

  • T是一个类,并创建了T的示例。
  • 调用由T声明的静态方法。
  • T声明的静态字段被赋值。
  • 使用T声明的静态字段,并且该字段不是常量变量(§4.12.4)。

因此,在访问INSTANCE静态字段之前,不会创建任何对象,Kotlinobject的所有成员都驻留在该字段中。这基本上就是getInstance试图实现的目标,不是吗?通过创建自己的getInstance,您可以重新发明轮子。
有人甚至会说LazyHolder是一个需要加载的额外类,因此在这方面比当前的实现更糟糕。
当你引入@JvmStatic/@JvmField时,事情变得更加复杂,这两个都允许你访问Kotlin属性,而不需要通过INSTANCE

object O {
    @JvmStatic
    val static = 0
    val notStatic = 1
}

在本例中,如果您刚刚访问了O.static,则会创建一个对象,而您提出的解决方案确实解决了这个问题。
但是,您提出的解决方案也使对象中init块的语义非常混乱-什么时候执行init块中的代码?
请允许我举一个相当做作的例子:

object O {
    @JvmStatic
    val static: Int
    val notStatic: Int

    init {
        timeConsumingSideEffect()
        val x = iDontWantToRunThisTwice()
        static = timeConsumingComputation(x)
        notStatic = timeConsumingComputation(x)
    }
}

它们应该放在OLazyHolder的静态初始化器中吗?
如果它们被放在O的静态初始化器中,那么它就不再是懒惰的了。只要你访问O.static,一切都会运行,就像当前的实现一样。
如果将它们放在LazyHolder的静态初始化器中,则其他代码可能会通过访问O.static获得未初始化的值。
虽然可以使用一些巧妙的数据流分析将init块中负责初始化@JvmStatic属性的代码与那些初始化常规属性的代码分开,但仍然不清楚timeConsumingSideEffect()何时应该运行。
语言设计很难!😅

相关问题