Kotlin thread save native lazy singleton с параметром

В java мы можем написать синглэты ad-save с помощью Double Checked Locking & volatile:

public class Singleton { private static volatile Singleton instance; public static Singleton getInstance(String arg) { Singleton localInstance = instance; if (localInstance == null) { synchronized (Singleton.class) { localInstance = instance; if (localInstance == null) { instance = localInstance = new Singleton(arg); } } } return localInstance; } } 

Как мы можем написать это в котлин?


Об объекте

 object A { object B {} object C {} init { C.hashCode() } } 

Я использовал декомпилятор kotlin, чтобы получить это

 public final class A { public static final A INSTANCE; private A() { INSTANCE = (A)this; ACINSTANCE.hashCode(); } static { new A(); } public static final class B { public static final AB INSTANCE; private B() { INSTANCE = (AB)this; } static { new AB(); } } public static final class C { public static final AC INSTANCE; private C() { INSTANCE = (AC)this; } static { new AC(); } } } 

Все объекты имеют конструктор, вызываемый в static блоке. Исходя из этого, мы можем думать, что это не лениво.

Приложите правильный ответ.

  class Singleton { companion object { val instance: Singleton by lazy(LazyThreadSafetyMode.PUBLICATION) { Singleton() } } } 

декомпилированные:

 public static final class Companion { // $FF: synthetic field private static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Singleton.Companion.class), "instance", "getInstance()Lru/example/project/tech/Singleton;"))}; @NotNull public final Singleton getInstance() { Lazy var1 = Singleton.instance$delegate; KProperty var3 = $$delegatedProperties[0]; return (Singleton)var1.getValue(); } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } 

Надеюсь, разработчики Kotlin в будущем сделают неотражающую реализацию …

У Kotlin есть эквивалент вашего Java-кода, но более безопасный. Проверка двойного замка не рекомендуется даже для Java . В Java вы должны использовать внутренний класс на статике, который также объясняется в идиоме Инициализации по требованию .

Но это Java. В Котлине просто используйте объект (необязательно ленивый делегат):

 object Singletons { val something: OfMyType by lazy() { ... } val somethingLazyButLessSo: OtherType = OtherType() val moreLazies: FancyType by lazy() { ... } } 

Затем вы можете получить доступ к любой переменной-члену:

 // Singletons is lazy instantiated now, then something is lazy instantiated after. val thing = Singletons.something // This is Doubly Lazy! // this one is already loaded due to previous line val eager = Singletons.somethingLazyButLessSo // and Singletons.moreLazies isn't loaded yet until first access... 

Котлин намеренно избегает путаницы у людей с одиночками на Java. И избегает «неправильных версий» этого шаблона, которого много. Вместо этого он обеспечивает более простую и безопасную форму синглетонов.

Учитывая использование lazy() , если у вас есть другие члены, каждый будет индивидуально лениться. И поскольку они инициализируются в лямбда, переданной lazy() вы можете делать то, о чем вы просили, о настройке конструктора и для каждого свойства элемента.

В результате у вас есть ленивая загрузка объекта Singletons ( при первом доступе экземпляра ), а затем более лёгкая загрузка something ( при первом доступе члена ) и полная гибкость в построении объекта.

Смотрите также:

  • функция lazy()
  • Опции режима «ленивый поток»
  • Объявления объектов

В качестве дополнительной заметки обратите внимание на библиотеки типов объектов для Kotlin, которые аналогичны инъекции зависимостей, предоставляя вам одиночные варианты с вариантами инъекции:

  • Injekt – я автор
  • Кодин – Очень похожее и хорошее

Объявление объекта именно для этой цели:

 object Singleton { //singleton members } 

Он ленив и потокобезопасен, он инициализируется при первом вызове, как и статические инициализаторы Java.

Вы можете объявить object на верхнем уровне или внутри класса или другого объекта.

Дополнительные сведения о работе с object s с Java см. В этом ответе .


Что касается параметра, если вы хотите достичь точно такой же семантики (первый вызов getInstance принимает свой аргумент для инициализации синглтона, следующие вызовы просто возвращают экземпляр, отбрасывая аргументы), я бы предложил эту конструкцию:

 private object SingletonInit { //invisible outside the file lateinit var arg0: String } object Singleton { val arg0: String = SingletonInit.arg0 } fun Singleton(arg0: String): Singleton { //mimic a constructor, if you want synchronized(SingletonInit) { SingletonInit.arg0 = arg0 return Singleton } } 

Основной недостаток этого решения состоит в том, что для определения object SingletonInit требуется отдельный object SingletonInit , чтобы скрыть object SingletonInit , и вы не можете напрямую ссылаться на Singleton до его инициализации.

Кроме того, см. Аналогичный вопрос об аргументах для одного синглета.

Недавно я написал статью по этой теме . TL; DR Вот решение, к которому я подошел:

1) Создайте класс SingletonHolder . Вам нужно только написать его один раз:

 open class SingletonHolder<out T, in A>(creator: (A) -> T) { private var creator: ((A) -> T)? = creator @Volatile private var instance: T? = null fun getInstance(arg: A): T { val i = instance if (i != null) { return i } return synchronized(this) { val i2 = instance if (i2 != null) { i2 } else { val created = creator!!(arg) instance = created creator = null created } } } } 

2) Используйте это в своих синглонах:

 class MySingleton private constructor(arg: ArgumentType) { init { // Init using argument } companion object : SingletonHolder<MySingleton, ArgumentType>(::MySingleton) } 

Инициализация синглтона будет ленивой и потокобезопасной.