Kotlin thread save native lazy singleton с параметром
В java мы можем написать синглэты ad-save, используя двойную проверенную блокировку и нестабильность:
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;
}
}
Как мы можем записать его в kotlin?
Об объекте
object A {
object B {}
object C {}
init {
C.hashCode()
}
}
Я использовал декомпилятор kotlin, чтобы получить это
public final class A {
public static final A INSTANCE;
private A() {
INSTANCE = (A)this;
A.C.INSTANCE.hashCode();
}
static {
new A();
}
public static final class B {
public static final A.B INSTANCE;
private B() {
INSTANCE = (A.B)this;
}
static {
new A.B();
}
}
public static final class C {
public static final A.C INSTANCE;
private C() {
INSTANCE = (A.C)this;
}
static {
new A.C();
}
}
}
Все объекты имеют конструктор, вызываемый в блоке 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 в будущем сделают нереляционную реализацию...
Ответы
Ответ 1
У 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...
Kotlin намеренно избегает путаницы у людей с одиночными играми на Java. И избегает "неправильных версий" этого шаблона, которого много. Вместо этого он обеспечивает более простую и безопасную форму синглетонов.
Учитывая использование lazy()
, если у вас есть другие члены, каждый будет индивидуально лениться. И поскольку они инициализируются в лямбда, переданной в lazy()
, вы можете делать то, о чем вы просили, о настройке конструктора и для каждого свойства элемента.
В результате у вас есть ленивая загрузка объекта Singletons
(при первом доступе экземпляра), а затем более лёгкая загрузка something
(при первом доступе элемента) и полная гибкость в построении объекта.
См. также:
В качестве дополнительной заметки просмотрите библиотеки типов объектов для Kotlin, которые аналогичны инъекции зависимостей, предоставив вам варианты с вариантами впрыска:
Ответ 2
Объявление объекта именно для этой цели:
object Singleton {
//singleton members
}
Он ленив и потокобезопасен, он инициализируется при первом вызове, так же как и статические инициализаторы Java.
Вы можете объявить object
на верхнем уровне или внутри класса или другого объекта.
Для получения дополнительной информации о работе с object
с 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
}
}
Основной недостаток этого решения состоит в том, что для определения singleton требуется отдельный файл, чтобы скрыть object SingletonInit
, и вы не можете ссылаться на Singleton
непосредственно до его инициализации.
Также см. аналогичный вопрос о предоставлении аргументов для одноэлемента.
Ответ 3
Недавно я написал статью по этому вопросу.
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)
}
Инициализация синглтона будет ленивой и потокобезопасной.